Server-defined UI is not a legend
Every developer over a certain age has a story about this. The form builder that handled eighty per cent of the screens and made the last twenty impossible. The XML that grew a scripting language inside it. The low-code tool that was wonderful until somebody asked for a layout it had no word for, and then was a prison. "The server decides the screen" is, for a lot of people, not an idea — it is a scar.
So I am not going to argue that the scar is unearned. It is earned. I am going to argue about one distinction, because in my experience it is the whole difference between the thing that failed and the thing that works:
The server describes the entity. It does not describe the screen.
What travels is what a field is: its name, the kind of value it holds, the rule it must satisfy, its allowed values, its label, its hint, which tab it belongs on, its order, and who may edit it. What never travels is a widget, a layout, a stylesheet, a template or a behaviour.
The client is not a renderer of instructions. It is a real application — in my case an Angular application with its own component library — that receives a description and decides how to draw it. That single line is why the last twenty per cent does not become impossible: anything the description cannot say, you write in code, in the client, like any other application. Nothing stands in the way.
What one field looks like
This is a real declaration, from the file that is running right now:
<field>
<name>status</name>
<sort>4</sort>
<label>Status</label>
<type>string</type>
<tooltip>Account status</tooltip>
<listvalues>
<value>O~Open</value>
<value>L~Locked</value>
<value>C~Closed</value>
</listvalues>
<validation>^(O|L|C)$</validation>
<nullable>N</nullable>
<default>O</default>
<readwrite>3</readwrite>
<affects3>Y</affects3>
</field>
Read what it says and, more importantly, what it does not.
It says the field is called status, that it is the fourth thing on its tab, that people see the word "Status", that its value is one of three codes, that a human should read those codes as Open, Locked and Closed, that it cannot be empty, that new accounts start Open, that it can be edited, and that changing it changes how the account looks in the tree.
It does not say "drop-down". It does not say where on the screen. It does not say what the control looks like, what happens on hover, or what the dialog does when the value changes. Those are the application's business, and the application is written by developers in the usual way.
Figure 1
What the browser does with it
The client holds no knowledge of any entity. Everything on the screen comes from one call that returns the dictionary:
- Tabs — one per declared layer, in order, with the layer's title.
- Field order — by
sort, within the tab. - Labels and hints — from
labelandtooltip. - Editable or not — from
readwrite; and a field markedpersonalis editable by a person on their own record even where it is read-only on someone else's. - Choices — a value list becomes a drop-down; the
code~Labelform shows the label and sends the code. - Empty values — a declared
nullmeaningis shown instead of a blank, so a screen can say[inherited]rather than leaving a hole. - Checking before sending — the rule, the limit and the required flag are applied in the dialog. The server applies the same rules again on arrival; the browser copy only saves a round trip.
Which produces the sentence that is the actual pay-off, and it is the reason to do any of this:
Adding a field to an entity is a server-side change only. Nothing in the browser has to know it happened.
Figure 2
The vocabulary is small, and that is the point
Here is the whole thing, counted this week:
| entity kinds declared | 16 |
| field declarations | 134 |
| tabs | 38 |
| the file | one, 1,796 lines |
| screens written for any of them | none |
The description vocabulary is fifteen elements — name, label, sort, type, tooltip, readwrite, nullable, default, validation, minmax, listvalues, format, nullmeaning, personal, affects3 — and the type list is twelve, from string and date through to a nested entity and a table that fills a tab.
That smallness is the design, not a limitation reached by accident. A vocabulary of fifteen elements can be held in one person's head, implemented completely, and finished. The builders that became prisons all had vocabularies that grew forever: every unsupported case added one more element, until the configuration language was a bad programming language and the escape hatch was the only part anyone used.
The rule that keeps it small is the one at the top. Describe the entity; never the screen. Screens are infinitely various, so a language for screens has to grow without limit. Entities are not: a field has a type, a rule, a name and an audience, and that list stops.
Figure 3
Figure 4
Where it earns its keep
One description, obeyed everywhere. The server validates with the rule; the API exposes the field; the browser checks the same rule before sending. There is no second copy in the front end that can disagree, which is the usual place this kind of thing quietly breaks: the back end tightens a rule, the dialog does not hear about it, and the user gets an error the form said was fine.
The shared vocabulary cannot go stale, because it is not documentation. If the business calls it a Locked account, the file says L~Locked, and that is what appears on the screen — change the file and the screen changes. There is no wiki page to fall behind.
The same entity looks different to different people, with no code deciding that. A field marked personal is editable on your own record and read-only on someone else's. Nobody wrote an if.
Figure 5
The honest half
No compiler checks a declaration. This is the real cost, and it is the mirror image of the benefit. Rich types in code get you compile-time checking; a declaration gets you one description that the server, the API and the browser all obey and that changes without a front-end release. Those are different trades and I would not pretend the second dominates the first. A typo in a rule is found at run time.
It states nouns, not verbs. The dictionary is the language of the data — fields, values, rules. The language of the actions lives elsewhere, in the command surface. A genuinely complete shared language would cover both, and this one does not yet. It is the clearest place the idea could go further.
The kinds are a closed list in a shared file. For one product that is a strength: one authoritative place, no drift. For several teams with different vocabularies it becomes one file everybody edits, and that is a queue.
And type does carry a hint of shape. I said no presentation travels, and that is very nearly true, but subentity, tablist and a tab-filling table do say something about form as well as about value. The file is public, so anyone can check that in a minute — better that I say it than that somebody finds it.
Where this applies, and where it does not
It applies to software whose screens are entities with rules — backoffice systems, administrative tools, anything where a hundred and thirty fields need to be shown correctly, guarded correctly, and changed often. That is a great deal of business software, and it is where hand-written screens are mostly a transcription job that goes stale.
It does not apply to a product whose interface is the product. If the screen is the thing being designed, describe nothing and write it. Nobody should be generating a landing page from a field dictionary.
The legend is only half a legend. The tools that scarred everybody failed for a reason you can name: they tried to describe screens. Describe entities instead, keep the vocabulary small enough to finish, and let a real application do the drawing — and what you get is not a prison. It is a hundred and thirty-four fields, no screens, and a business language that cannot drift.
The system is Esquire, a framework for backoffice systems — Java and Spring Boot, an Angular front end, Postgres or Oracle, identity synchronised to Keycloak. The dictionary in this article is esq-entity-dictionaries.xml, and the design document beside it explains every element. Open any entity in the running deployment and the dialog you get was never written. The four repositories are public at github.com/mir0n-pro. A deployment is running at esquire.mir0n.pro — sign in with mainadmin / q, it is a demonstration tree and it is seeded fresh with each release.