WebItTNG

Eine laufende VCL-Anwendung im Browser bedienen

Komponente aufs Formular, Active := True, fertig. Die Anwendung selbst wird nicht geändert – kein Umbau, keine zweite Oberfläche, kein zweiter Datenpfad.

Delphi 5 und RAD Studio 13 aus einer Quelle keine Fremdbibliothek, kein Framework 792 automatische Prüfungen entstanden mit Kai und Claude Code
Links die laufende VCL-Maske, rechts dieselbe Maske im Browser. Dazwischen wandern Zustandsbaum und Deltas zum Browser, Werte und Klicks zurück.

Das Problem

Gewachsene Delphi-Anwendungen laufen zuverlässig, aber sie laufen auf genau einem Rechner, vor dem jemand stehen muss.

Irgendwann kommt die Frage: „Kann ich da auch vom Tablet draufschauen?“ Die üblichen Antworten sind teuer. Fernwartung überträgt Pixel und fühlt sich auch so an. Eine Neuentwicklung als Webanwendung bedeutet, zwanzig Jahre Fachlogik ein zweites Mal zu schreiben – und ab dann zwei Oberflächen zu pflegen, die auseinanderlaufen. Ein Umbau auf ein Web-Framework verlangt genau das, was es bei einer Anwendung aus dem letzten Jahrtausend nicht gibt: eine saubere Trennung von Oberfläche und Logik.

WebItTNG nimmt einen anderen Weg. Es liest das laufende Formular – die Controls, die ohnehin da sind, mit ihren Werten von gerade eben – und baut daraus im Browser echte HTML-Elemente. Was der Bediener dort tut, kommt als Wert oder Klick im Formular an, auf demselben Weg, den die VCL selbst benutzt. Die Anwendung merkt nicht, dass sie ferngesteuert wird.

Der ganze Einbau

Eine Komponente, eine Zeile. Alles Weitere ist optional.

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Wit.Port := 8080;
  Wit.Active := True;
end;

Der Browser zeigt danach dasselbe Formular, das auf dem Bildschirm steht. Wechselt die Anwendung das Formular oder öffnet sie einen modalen Dialog, wechselt der Browser mit.

Wie es funktioniert

Der Browser fragt nicht nach. Er hält eine Verbindung offen und bekommt nur, was sich geändert hat.

Alle 300 Millisekunden baut der Server eine Momentaufnahme des Formulars und vergleicht sie mit der vorigen. Was gleich geblieben ist, wird nicht gesendet – eine ruhende Maske kostet keine Bandbreite. Was sich geändert hat, geht als kleines JSON-Paket über einen Server-Sent-Events-Kanal an den Browser, der daraufhin genau die betroffenen Elemente ändert. Kein Neuaufbau, kein Flackern, keine verlorene Cursorposition.

Zwei Momentaufnahmen des Formulars werden verglichen. Nur der Unterschied geht als kleines JSON-Paket an den Browser.
Gemessen an der mitgelieferten Demo: Vollzustand 2262 Byte, ein Delta 148 Byte.

Controls ohne HTML-Gegenstück

Ein selbstgezeichnetes Control oder eine Fremdkomponente hat im Browser keine Entsprechung. Solche Controls reisen als Bild – aber nicht das Bild selbst, sondern nur eine Prüfsumme seiner Pixel. Das Bild holt der Browser unter einer URL, die diese Prüfsumme enthält. Ändert sich das Bild, ändert sich die URL; ändert es sich nicht, kostet ein erneuter Abruf gar nichts, weil der Browser die URL hart zwischenspeichern darf.

Schrift und Farbe erben

Übertragen wird nur, was von der Umgebung abweicht. Das Formular trägt die Grundschrift, alles darunter erbt sie im Browser über ganz normales CSS – in der Demo sind das zwei Schriftobjekte auf neunzehn Knoten. Dasselbe gilt für die Hintergrundfarbe: kein Wert heißt „der Elternknoten scheint durch“, genau wie ParentColor in der VCL.

Tastatur und Barcodescanner

Ein Barcodescanner tippt seinen Code als schnelle Tastendrücke und schließt mit einem Wagenrücklauf ab. Damit das funktioniert, reicht es nicht, den Text zu übertragen: die Anwendung wartet auf genau dieses Zeichen – in OnKeyPress, oder weil ein Default-Button darauf reagiert. WebItTNG bildet die Reihenfolge nach, in der die VCL eine Taste zustellt: erst die Tastenkürzel des Formulars, dann der Dialogschlüssel, dann das fokussierte Control. Der Default-Button klickt, als hätte jemand davorgesessen.

Wie es entstand: Kai und Claude Code

Dieses Paket wurde nicht von Hand geschrieben und anschließend von einem Assistenten aufgeräumt. Es ist vollständig im Gespräch entstanden – Claude Code als Agent, Kai als dessen Zugang zu RAD Studio. Wichtiger als diese Überschrift ist allerdings die Arbeitsweise.

Der Aufbau

RAD Studio 13 bleibt die ganze Zeit offen. Kai ist die Brücke hinein: eine Datei so lesen, wie sie gerade im Editorpuffer steht und nicht wie sie auf der Platte liegt, Änderungen in diesen Puffer schreiben, compilieren, das Projekt verwalten, den Debugger steuern. Claude Code läuft im Projektverzeichnis und erledigt den Rest – suchen, schreiben, die Testprogramme laufen lassen, die Demo starten, eine Seite vom laufenden Server abholen.

Die Aufgabenteilung, die sich dabei herausgebildet hat, ist erwähnenswert, weil sie nicht die naheliegende ist: Die IDE-Brücke ist für alles richtig, was Dateien betrifft, die im Editor offen sind – dort würde ein Schreibvorgang an der IDE vorbei ungespeicherte Arbeit verlieren oder still wieder überschrieben werden. Alles andere – eine Konsolen-Testsuite laufen lassen, Bytezahlen vergleichen, einen Browser steuern – geht außerhalb der IDE schneller. Diese Grenze falsch zu ziehen kostet echte Zeit: eine Änderung, die auf die Platte geschrieben wurde, während dieselbe Unit im Designer offen stand, war schlicht wieder überschrieben – und sah eine Weile lang wie ein Compilerproblem aus.

Wie eine Anforderung ankam

Fast nie als Spezifikation. Meist als zwei Sätze Fließtext, und sehr häufig als Beobachtung an der laufenden Anwendung:

„Hintergrundfarbe wird immer noch ignoriert. Eingestellt ist Color = 1, also fast schwarz, angezeigt wird weiß. Dann: Der Testdialog hat ein Panel in gelb, dadrauf ist ein Label mit schwarzem Hintergrund. Dadurch bekommt das Label einen breiten gelben Rand. Im Browser ist der Rand weiß.“

Das ist eine gute Fehlermeldung und eine schwierige: zwei Symptome, eines davon falsch zugeordnet. Daraus eine Änderung zu machen ist die eigentliche Arbeit, und dort steckt der größte Teil des Aufwands in diesem Projekt. Für Funktionswünsche gilt dasselbe: „Kann man die Verbindungsanzeige ausblenden, und kann der Browser sich selbst wieder verbinden?“ kam mit Vorschlägen für die Eigenschaftsnamen – und ein Teil der Antwort bestand darin, einen besseren Zuschnitt vorzuschlagen: ein Intervall, bei dem null „gar nicht“ heißt, statt eines Schalters neben einem Intervall, die sich widersprechen können.

Wie ein Fehler tatsächlich gefunden wurde

Die Meldung oben ist ein durchgespieltes Beispiel der Methode – und dafür, warum Raten gescheitert wäre. Es brauchte fünf Schritte:

Das zweite Symptom entpuppte sich als eigener Fehler in derselben Meldung: ein Panel, das das ganze Formular abdeckt, durfte für dessen Farbe sprechen – richtig, solange die Formularfarbe die Vorgabe des Designers ist, falsch, sobald jemand sie absichtlich gesetzt hat. Beides wurde behoben, beides festgenagelt, und die Darstellung im Browser gegen eine Seite geprüft, die aus dem ausgelieferten Stylesheet gebaut wurde.

Debuggen ohne Debugger

Breakpoints kamen sparsam und mit Absicht zum Einsatz. Eine lange Live-Debug-Sitzung gegen eine GUI-Anwendung, die nebenher HTTP bedient, ist fragil, und Schrittbetrieb beantwortet eine Frage langsam. Drei Techniken haben das fast vollständig ersetzt:

792 Prüfungen, jedes Mal

Tests waren keine Phase am Ende. Jede Änderung ging zusammen mit ihrer Prüfung hinein, und danach lief der gesamte Satz – nicht der betroffene Teil, der gesamte Satz. Das dauert unter einer Minute, und genau das macht die Gewohnheit durchhaltbar.

454Pascal-Prüfungen ohne VCL
242JavaScript-Prüfungen des Clients
96Urteile an echten Controls

Der Browser-Client verdient einen eigenen Hinweis. Er besteht aus erzeugten Pascal-Zeichenketten – also genau der Art Code, die sich dem Testen sonst entzieht. Deshalb gibt die Testsuite die Seite aus, die tatsächlich ausgeliefert wird, extrahiert das Skript und fährt es gegen einen nachgebauten DOM. Und weil die interessante Hälfte einer Einstellung diejenige ist, die etwas abschaltet, gibt sie eine zweite Seite mit allem abgeschaltet aus. Ein Client, der die Schalter schlicht ignoriert, bestünde sonst jede Prüfung.

Jede neue Zusicherung wurde gegengetestet: Korrektur zurückdrehen, das Umfallen bestätigen, wiederherstellen. Diese Gewohnheit hat Zusicherungen entlarvt, die bedeutsam aussahen und nichts prüften – und sie ist der Grund, warum die Zahlen oben überhaupt etwas wert sind.

Dokumentation als Teil der Änderung

Zwei Dokumente werden im selben Schritt wie der Code aktuell gehalten: ein README, das erklärt, wie die Sache funktioniert und warum jede Entscheidung so ausgefallen ist, und ein Übergabedokument für die nächste Sitzung – nicht verhandelbare Konventionen und eine wachsende Liste von Fehlern, die je einen Tag gekostet haben, damit sie sich nicht wiederholen.

Die Kommentare folgen derselben Regel. Sie sagen nicht, was die Zeile tut, sondern warum sie so ist – und nennen häufig den Fehlschlag, der sie verursacht hat. „Nur den Stil zu melden ergab ein graues Härchen, wo die Anwendung einen breiten Farbstreifen zeigt“ ist in einem halben Jahr mehr wert als jede Wiederholung des Codes.

Zwei Compiler, einer davon unerreichbar

Die Zwei-Compiler-Anforderung hat die Zusammenarbeit stärker geprägt als alles andere. RAD Studio 13 steht auf dem Entwicklungsrechner, Delphi 5 nicht. Jeder D5-Fehler kam deshalb als eingefügter Text zurück und musste allein daraus behoben werden.

Das funktioniert besser, als es klingt – mit einer harten Lektion: niemals einen Namen raten, den nur der andere Rechner kennt. Ein benötigter Package-Name wurde einmal geraten, was den Build auf einen Pfad schickte, der zwei weitere Fehlermeldungen und eine falsche Schlussfolgerung produzierte. Den richtigen Namen nachzusehen kostete den Anwender zehn Sekunden. „Das sehe ich von hier aus nicht, bitte nachschauen“ ist die schnellere Antwort als eine plausible Erfindung.

Was schiefging

Konkret zu werden ist hier nützlicher als eine saubere Erzählung:

Nichts davon spricht gegen den Ansatz. Es ist der Grund, warum die Prüfungen genau die Form haben, die sie haben: jede einzelne ist ein konkreter Fehlschlag, der einmal passiert ist.

Was dafür sorgt, dass es trägt

Adapter statt Sonderfälle

Jede Control-Klasse wird von einem Adapter beschrieben; gesucht wird der nächste registrierte Vorfahr. Eine Anwendung registriert für ihr eigenes Control acht Zeilen – und aus dem Bild wird ein richtiger Knopf. Das Paket muss dazu nicht angefasst werden.

Vier Layout-Modi

Eins zu eins wie entworfen, waagerecht zentriert, mittig, oder untereinander fließend für schmale Geräte. Dazu unabhängig davon die Skalierung: Originalgröße, ins Fenster einpassen, auf Breite.

Fenster oder Dienst

Dieselbe Exe startet als normales Fenster oder als Windows-Dienst. Dass ein VCL-Formular auch ohne angemeldeten Benutzer zeichnet, ist nachgemessen und nicht vermutet – die Messung steckt als Diagnosefunktion mit im Paket.

Unterschrift, Download, Video

Drei Dinge, die es in der VCL so nicht gibt: eine Fläche zum Unterschreiben mit dem Finger, ein Link, der eine Datei herunterlädt, ein Bild, das sich selbst auffrischt. Die Anwendung beschreibt sie über einen Adapter.

Eigenes Stylesheet, eigene Endpunkte

Jeder Knoten trägt seine VCL-Klasse als CSS-Klasse mit sich. Eine Regel erreicht damit alle Buttons einer Bibliothek auf einmal. Und eine Anwendung kann eigene URLs bedienen, ohne einen zweiten Server zu starten.

Namen verbergen

Statt frmMain.pnlLinks.btnFreigabe reist auf Wunsch ein 64-Bit-Hash des Pfades. Das ist Verschleierung, keine Sicherheit – und die Dokumentation sagt das an genau der Stelle, an der man es liest.

Verbindung sichtbar

Ein Schild sagt, ob der Browser noch hängt. Bricht die Verbindung ab, versucht er sie in einstellbarem Abstand wieder aufzubauen und kann in der Zwischenzeit einen bildschirmfüllenden Hinweis zeigen – damit ein Terminal ohne Verbindung nicht bedienbar aussieht.

Anmeldung

HTTP Basic gegen feste Zugangsdaten oder gegen einen eigenen Handler – dann entscheidet die Anwendung selbst, etwa gegen ihre Datenbank oder die Domäne.

Keine Abhängigkeiten

Kein Indy, kein HTTP-Server von der Stange, keine JavaScript-Bibliothek. Der Transport geht direkt auf Winsock, der Browser-Client ist reines ES5. Was nicht da ist, kann auch nicht veralten.

Aufbau

Sieben Schichten, jede ohne Kenntnis der darüber liegenden. Nur die beiden obersten kennen die VCL.

Sieben Schichten von der Komponente bis zu den Grundtypen. Nur die oberen beiden brauchen die VCL.

Diese Trennung ist der Grund, warum das Paket prüfbar ist. Alles, was kein Formular braucht – Zustandsbaum, Delta-Bildung, HTTP, JSON, Prüfsummen, der ganze Browser-Client – wird in einem Konsolenprogramm getestet. Die VCL-Hälfte läuft über zwei Diagnoseprogramme, die echte Controls erzeugen und das Ergebnis Zeile für Zeile ausweisen.

Zwei Compiler, eine Quelle

Dieselben Dateien übersetzt Delphi 5 von 1999 und RAD Studio 13. Nicht zwei Zweige, nicht zwei Kopien.

Dieselben Quelldateien werden von Delphi 5 und von RAD Studio 13 übersetzt, unterschieden nur durch zwei Schalter.

Das ist keine Spielerei, sondern der Punkt: die Anwendungen, die diesen Weg in den Browser am nötigsten haben, sind genau die alten. Der Preis dafür ist Disziplin – keine Generics, keine inline deklarierten Variablen, kein $IF, keine Klassenhelfer, und ein byteorientierter Zeichenkettentyp, weil ein string auf beiden Compilern etwas anderes bedeutet. Unterschieden wird an genau einer Stelle, über zwei Schalter in einer Include-Datei.

Zahlen

Gemessen, nicht geschätzt.

454Prüfungen ohne VCL
242Prüfungen des Browser-Clients
96Prüfungen an echten Controls
148 Bein Delta in der Demo
0 Bbei unverändertem Formular
300 msStandardtakt
Formatdasselbe Control als Bild
BMP (Delphi 5)46 854 Byte
PNG (RAD Studio 13)460 Byte

Grenzen

Was das Paket nicht kann und nicht sein will.

Nicht ans Internet. Das ist für das Firmennetz gebaut. Die Anmeldung ist HTTP Basic – das ist Kodierung, keine Verschlüsselung: wer die Verbindung mitlesen kann, liest die Zugangsdaten mit. Auf einem geschlossenen Netz ist das in Ordnung, darüber hinaus nicht.

Ein Formular, eine Anwendung: der Browser sieht, was auf dem Server-Bildschirm steht. Wer für jeden Benutzer eine eigene Sitzung braucht, braucht etwas anderes.