WebItTNG

Operate a running VCL application from a browser

Drop the component on the form, set Active := True, done. The application itself is not changed – no rewrite, no second user interface, no second data path.

Delphi 5 and RAD Studio 13 from one source no third-party library, no framework 792 automated checks written with Kai and Claude Code
On the left the running VCL form, on the right the same form in a browser. State tree and deltas travel to the browser, values and clicks come back.

The problem

Long-lived Delphi applications run reliably – and they run on exactly one machine, with somebody standing in front of it.

Sooner or later somebody asks whether they could look at it from a tablet. The usual answers are expensive. Remote desktop ships pixels and feels like it. Rewriting the thing as a web application means writing twenty years of business logic a second time – and maintaining two user interfaces from then on, which will drift apart. Retrofitting a web framework demands precisely what an application from the last millennium does not have: a clean separation between logic and presentation.

WebItTNG takes a different route. It reads the running form – the controls that are there anyway, with the values they hold right now – and builds real HTML elements from them. Whatever the operator does over there arrives in the form as a value or a click, along the same path the VCL itself uses. The application never notices it is being driven remotely.

The entire integration

One component, one line. Everything else is optional.

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

From then on the browser shows the same form that is on the screen. If the application switches forms or opens a modal dialog, the browser follows.

How it works

The browser does not poll. It holds one connection open and receives only what has changed.

Every 300 milliseconds the server takes a snapshot of the form and compares it with the previous one. Whatever stayed the same is not sent – a form at rest costs no bandwidth at all. Whatever changed travels as a small JSON frame over a Server-Sent Events channel, and the browser updates exactly the elements concerned. No rebuild, no flicker, no lost caret position.

Two snapshots of the form are compared. Only the difference travels to the browser as a small JSON frame.
Measured on the bundled demo: full state 2262 bytes, one delta 148 bytes.

Controls with no HTML counterpart

An owner-drawn control or a third-party component has no equivalent in the browser. Those travel as a picture – but what is sent is not the picture, only a checksum of its pixels. The browser fetches the image from a URL that contains that checksum. If the picture changes, the URL changes; if it does not, fetching it again costs nothing at all, because the browser is allowed to cache that URL hard.

Fonts and colours are inherited

Only what deviates from its surroundings is transmitted. The form carries the base font and everything below it inherits through ordinary CSS – in the demo that is two font objects across nineteen nodes. The same goes for background colours: no value means “the parent shows through”, exactly like ParentColor in the VCL.

Keyboards and barcode scanners

A barcode scanner types its code as fast key strokes and finishes with a carriage return. Sending the text across is not enough for that: the application is waiting for precisely that character – in OnKeyPress, or because a default button responds to it. WebItTNG reproduces the order in which the VCL itself delivers a key: the form's shortcuts first, then the dialog key, then the focused control. The default button clicks as if somebody had been sitting in front of it.

How it was built: Kai and Claude Code

This package was not written by hand and then tidied up by an assistant. It was produced end to end in a conversation – Claude Code as the agent, Kai as its way into RAD Studio – and the working method matters more than that headline.

The setup

RAD Studio 13 stays open the whole time. Kai is the bridge into it: reading the file the way it currently is in the editor buffer rather than on disk, applying edits into that buffer, compiling, managing the project, and driving the debugger. Claude Code runs in the project directory and does the rest – searching, writing, running the test programs, starting the demo, fetching a page from the running server.

The division of labour that emerged is worth stating, because it is not the obvious one: the IDE bridge is best for anything touching files that are open in the editor, where writing to disk behind the IDE's back would lose unsaved work or be silently overwritten again. Everything else – running a console test suite, comparing byte counts, driving a browser – is faster outside the IDE. Getting that boundary wrong costs real time: an edit written to disk while the same unit sat open in the designer was simply saved over, and the mistake looked like a compiler problem for a while.

How a requirement arrived

Almost never as a specification. Usually as two sentences of prose, and very often as an observation from the running application:

“The background colour is still being ignored. It is set to Color = 1, almost black, and what shows is white. And: the test dialog has a yellow panel with a label on it that has a black background. That gives the label a wide yellow border. In the browser that border is white.”

That is a good bug report and a hard one: two symptoms, one of them misattributed. Turning it into a change is the actual work, and it is where most of the effort in this project went. The same applies to features: a request like “can the connection badge be switched off, and can it reconnect by itself?” arrived with suggested property names, and part of the answer was to propose a better shape – one interval where zero means off, instead of a flag and an interval that can contradict each other.

How a defect was actually found

The report above is a worked example of the method, and of why guessing would have failed. It took five steps:

The second symptom turned out to be a different defect in the same report: a panel covering the whole form was allowed to speak for the form's colour, which is right when the form colour is the designer's default and wrong when somebody set it on purpose. Both were fixed, both were pinned, and the browser rendering was checked against a page assembled from the shipped stylesheet.

Debugging without a debugger

Breakpoints were used sparingly and on purpose. A long live debugging session against a GUI application that is simultaneously serving HTTP is fragile, and stepping through it answers one question slowly. Three techniques replaced it almost entirely:

792 checks, run every time

Tests were not a phase at the end. Every change went in together with its check, and the whole set ran afterwards – not the affected part, the whole set. It takes under a minute, which is what makes the habit survivable.

454Pascal checks, no VCL needed
242JavaScript checks of the client
96verdicts against real controls

The browser client deserves a note of its own. It is generated Pascal strings, which is exactly the kind of code that normally escapes testing. So the test suite dumps the page that actually ships, extracts the script, and runs it against a stand-in DOM – and since the interesting half of a setting is the half that switches something off, it dumps a second page with everything disabled. A client that simply ignored the switches would pass otherwise.

Every new assertion was mutation-tested: revert the fix, confirm the test fails, restore. That habit caught assertions that looked meaningful and checked nothing – and it is the reason the numbers above are worth quoting at all.

Documentation as part of the change

Two documents are kept current in the same step as the code: a README that explains how the thing works and why each decision went the way it did, and a handoff document that exists for the next session – conventions that are not negotiable, and a growing list of mistakes that cost a day each, so they are not repeated.

The comments follow the same rule. They do not say what the line does; they say why it is that way, and frequently name the failure that caused it. “Reporting only the frame put a grey hairline where the application shows a broad colour band, which is how this was found” is more useful in six months than any restatement of the code.

Two compilers, one of them out of reach

The dual-compiler requirement shaped the collaboration more than anything else. RAD Studio 13 is on the development machine; Delphi 5 is not. Every D5 error therefore came back as text pasted into the conversation and had to be fixed from that alone.

That works better than it sounds, with one hard lesson: never guess a name that only the other machine knows. A required package name was guessed once, which sent the build down a path that produced two further errors and a wrong conclusion. The correct name took the user ten seconds to look up. “I cannot see that from here, please check” is a faster answer than a plausible invention.

What went wrong

Being specific here is more useful than a clean story:

None of this is an argument against the approach. It is the reason the checks exist in the form they do: each one is a specific failure that happened once.

What makes it hold up

Adapters instead of special cases

Every control class is described by an adapter, resolved to the nearest registered ancestor. An application registers eight lines for a control of its own – and what was a picture becomes a real button. The package itself is never touched for it.

Four layout modes

One to one as designed, centred horizontally, centred both ways, or flowing one below the other for narrow devices. Scaling is independent of that: original size, fit the window, fit the width.

Window or service

The same executable starts as an ordinary window or as a Windows service. That a VCL form paints with nobody logged on was measured rather than assumed – the measurement ships as a diagnostic inside the package.

Signature, download, video

Three things the VCL does not have: an area to sign with a fingertip, a link that downloads a file, an image that refreshes itself. The application describes them through an adapter.

Own stylesheet, own endpoints

Every node carries its VCL class name as a second CSS class, so one rule reaches every button of a component library at once. And an application can serve URLs of its own without starting a second server.

Hiding names

Instead of frmMain.pnlLeft.btnRelease, a 64-bit hash of the path can travel. That is obfuscation, not security – and the documentation says so in the very place where it is read.

Connection state

A small badge says whether the browser is still attached. If the connection drops, it retries at a configurable interval and can cover the screen with a notice meanwhile – so a terminal without a connection does not look operable.

Authentication

HTTP Basic against fixed credentials, or against a handler of your own – then the application decides for itself, against its database or the domain, whatever it already uses.

No dependencies

No Indy, no off-the-shelf HTTP server, no JavaScript library. The transport sits directly on Winsock and the browser client is plain ES5. What is not there cannot go stale.

Structure

Seven layers, none of them aware of the ones above. Only the top two know the VCL exists.

Seven layers from the component down to the base types. Only the top two need the VCL.

That separation is the reason the package can be tested at all. Everything that needs no form – state tree, delta building, HTTP, JSON, checksums, the entire browser client – is tested from a console program. The VCL half runs through two diagnostic programs that create real controls and report the result line by line.

Two compilers, one source

The same files are compiled by Delphi 5 from 1999 and by RAD Studio 13. Not two branches, not two copies.

The same source files are compiled by Delphi 5 and by RAD Studio 13, told apart by two switches only.

This is not a stunt, it is the point: the applications that need a way into the browser most urgently are precisely the old ones. The price is discipline – no generics, no inline variable declarations, no $IF, no class helpers, and a byte-oriented string type, because string means something different on each compiler. The two are told apart in exactly one place, by two switches in an include file.

Numbers

Measured, not estimated.

454checks without the VCL
242checks of the browser client
96checks against real controls
148 Bone delta in the demo
0 Bwhile the form does not change
300 msdefault interval
Formatthe same control as a picture
BMP (Delphi 5)46,854 bytes
PNG (RAD Studio 13)460 bytes

Limits

What the package cannot do, and does not want to be.

Not for the internet. This is built for a company network. Authentication is HTTP Basic – which is encoding, not encryption: anyone able to watch the connection reads the credentials with it. On a closed network that is fine; beyond it, it is not.

One form, one application: the browser sees what is on the server's screen. Anybody who needs a separate session per user needs something else.