TMS WEB Core compiles Delphi code to JavaScript with pas2js. An AI coding assistant knows a
great deal about Delphi and a great deal about JavaScript, but very little about the narrow
space between them. Asked to do something in the browser, it reaches for what it knows best:
an asm block full of JavaScript.
That code works, but it is code the Pascal compiler cannot check, the IDE cannot navigate, and the next person to read the project has to understand in two languages. The aim of this article is to keep the assistant writing Pascal, and to give it the WEB Core and XData knowledge it lacks.
For setting up Claude Code on a Delphi project in general, see Using Claude Code on a Delphi Project; this article covers what is different for the web.
Most of what an assistant puts in an asm block can be written in Pascal. The
browser's objects are available as Pascal classes in the WEB and JS
units, so this:
asm
document.getElementById('status').innerHTML = 'Saved';
end;
is simply:
document.getElementById('status').innerHTML := 'Saved';
with WEB in the uses clause. The Pascal version is checked by the compiler,
found by Find References, and survives a rename. Better still, bind the element to a WEB Core
control through its ElementID and set the control's properties.
Say it plainly in CLAUDE.md: no asm blocks unless there is no
Pascal way, and if there is not, ask first. Assistants follow that rule well once it is
written down.
The other habit to break is setting colours, sizes and visibility in code. An assistant asked to "make the error message red" will set a style property from Pascal - or, worse, from JavaScript. In a web project the right place is a CSS class in the stylesheet, and the code only switches classes:
Error_Label.ElementClassName := 'error_text';
This keeps the look of the application in one place, where a designer - or you, later - can
change it without recompiling. HTML and CSS are also where an assistant is at its strongest,
so let it work on the form's .html template and the stylesheet freely.
pas2js accepts most of modern Delphi, but not all of it, and an assistant will write desktop Delphi unless told otherwise. The common failures:
AnsiChar, AnsiString,
ShortString, PChar, Variant, variant records and
SizeOf. The full list, with what to use instead, is in
Sharing Units Between VCL and TMS WEB
Core.Int64 is only exact to 53 bits in the browser and
Currency is not exact. Code that passes large IDs between server and browser
needs the approach in
Sending Int64 Values to
JavaScript.Tag property to
carry the context instead.nil where a procedure is expected
behaves differently from Delphi; see
nil pointers in TMS WEB Core.Each of these is one line in CLAUDE.md, and each line saves a round of "that
does not compile in the browser".
The quirk that causes most confusion: when XData sends an object as JSON, fields whose
names start with F lose the F. A class with a field
FCustomer_Id arrives in the browser as Customer_Id. An assistant
writing the client side will look for FCustomer_Id, find nothing, and - without
an error - carry on with an empty value. When it builds JSON to send, it may add the
F and have the server ignore the field.
It also does not know your service contracts unless it can read them. Give it the folder
with the server's interface units, even if they belong to a different project, and say in
CLAUDE.md where they are. Whether a service operation is called with GET or POST
changes how the client calls it - covered in
GET or POST for TMS XData Service
Operations.
# Customer Portal - TMS WEB Core client, XData server
## Language
- This is pas2js, not desktop Delphi. Code must compile with pas2js.
- No asm blocks. Use the WEB and JS units for browser objects. If
something cannot be done in Pascal, stop and say so before writing JS.
- No AnsiChar, AnsiString, ShortString, PChar, Variant or SizeOf.
- Int64 is exact only to 2^53. IDs from the server arrive as strings.
- Do not pass nested procedures as callbacks to overloaded TMS methods;
use a form method or a component event.
- Forms load asynchronously: only use a form's controls once it has loaded.
## Styling
- All styling in CSS (css\portal.css). Code only sets ElementClassName.
- Form layout is in each form's .html template.
## XData
- Server interfaces: ..\Server\Services\*.pas (read only).
- JSON drops the leading F from field names: FCustomer_Id is Customer_Id.
## Build
- I build in the IDE and paste errors. Say which files you changed.
asm block should be one
you agreed to.WEB and JS units instead of
asmF, and access to the server's interfaces