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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The report above is a worked example of the method, and of why guessing would have failed. It took five steps:
/state, and that exists precisely so a report can
be replaced by evidence. The dump showed the panel reporting no colour at
all and a grey hairline border where the application showed a broad
yellow band.BorderColor across BorderWidth
pixels, independently of both frame styles. The adapter treated the three
as mutually exclusive, so a 30-pixel yellow band was reported as a
one-pixel grey line.tblr:#9f9f9f –
literally the grey hairline the user had been looking at. A test that
does not fail when the bug comes back is decoration.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.
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:
/state returns the whole tree as JSON. Two probe programs
create real controls and print one verdict per line. A separate
measurement answers “does this form actually paint with nobody
logged on?” with pixels rather than opinion./state JSON was fed through
the real browser client in a test harness, and the resulting styles were
dumped. That distinguishes “the server is wrong” from
“the client is wrong” in one run, without a debugger and
without access to the machine where it happened.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
Seven layers, none of them aware of the ones above. Only the top two know the VCL exists.
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.
The same files are compiled by Delphi 5 from 1999 and by RAD Studio 13. Not two branches, not two copies.
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.
Measured, not estimated.
| Format | the same control as a picture |
|---|---|
| BMP (Delphi 5) | 46,854 bytes |
| PNG (RAD Studio 13) | 460 bytes |
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.