logo
30 Jul 2026

The Ontology, Part 6: The Two Apps

Part 6 of the beginner's guide to ontologies — the frontend, at last. A frontend on an ontology is just the read and write models rendered. There are two apps: a Builder that edits the rulebook (definitions) and a Client that edits the data — and because properties are typed, the Client renders itself from the definition. One shared foundation, two apps, with a live demo.

This is part 6 — the frontend, finally. The good news: there's almost nothing new to learn. A frontend on an ontology is just the pieces you already have, wired to pixels — the read model from part 3, the write model from part 5, and the typed building blocks from part 4. This part is the mental model that ties them together: an ontology has two apps, and once you see why, the rest falls into place. There's a live demo of both apps below.


A frontend is just the models, rendered

Before anything else, the reframe that makes this whole topic small. You've spent five parts building a data layer: typed objects, an object-set query model for reading, an action model for writing, security that travels with every call. A frontend doesn't add a new system on top of that — it renders it.

  • A table on screen? That's part 3's fetchPage with some <tr> tags around it.
  • A save button? That's part 5's action, with the optimistic update and rollback you already saw.
  • A filter dropdown, a detail card, a chart — each is one of the read or write operations you already know, wearing a UI.

So the only real question this part answers is: what consumes those operations, and how? And the first surprising answer is that it's not one app. It's two.

Two planes, two apps

Think back to where the ontology's pieces live. There are two very different kinds of thing in the system:

  • The definitions — the rulebook. Object types, their properties and types, links, actions, security policies. This changes rarely, and only a few people touch it.
  • The data — the instances. The actual customers, orders, alerts. This changes constantly, and lots of people touch it.

These two planes call for two different frontends, and the cleanest way to tell them apart is which plane each one edits.

Builder app internal modelers — edits the rulebook Client app end users — edits the data Definition plane types · properties · links · policies Data plane the actual objects (instances) edits edits

The everyday version of this split is a spreadsheet. Setting up the columns — naming them, deciding this one is a date and that one is a dropdown of three choices — is the builder. Typing values into the rows is the client. Almost everyone only ever fills in rows; a few people design the sheet. (If you come from databases, it's the same line as schema-vs-rows, or DDL-vs-DML — the builder changes the shape, the client changes the contents.)

👩‍💻 Mai (junior): Why not just build two normal apps and be done? Why make a thing of it?

👨‍🏫 Hamed (mentor): Because they're not really two separate apps — they're two faces of one system, and missing that leads you to build everything twice. The Builder writes a definition; the Client turns around and reads that same definition to know what to show. They share a spine. Name the split and you get to build that spine once.

The shared foundation

Here's that spine. Because both apps talk to the same ontology, they stand on one shared foundation — three pieces, built once:

Builder app Client app SHARED FOUNDATION typed data client query + actions metadata contract the self-description component kit AutoField · ObjectTable the ontology — definition plane · data plane

The typed data client is the read and write models from Parts 3 and 5 in code form — fetchPage, count, actions with their optimistic/rollback behavior. The metadata contract is the ontology describing itself at runtime: "the Asset type has a status property that's an enum of these three values." The component kit is a small set of renderers — AutoField for inputs, ObjectTable for tables — that read that metadata and draw the right thing.

Build those three once, and each app is thin on top. The Builder is mostly forms over the definition plane; the Client is mostly the kit pointed at the data plane. Neither reinvents how to talk to the ontology.

The trick that makes it work: typed data renders itself

This is the idea worth slowing down for, because it's why one foundation can serve two apps. Remember from part 4 that every property has a type — text, number, date, enum, location. And the ontology exposes that typing at runtime. Put those together and a component can ask "what am I rendering?" and pick the right widget on its own:

  • a date property → a date picker, and in a table, a formatted date
  • an enum → a dropdown of exactly its allowed values
  • a boolean → a toggle, shown as yes/no in a table
  • a location → a map pin (this is Part 2's ILocatable paying off — it only works because location is a real geo type)
  • a to-many link → an expandable list; a to-one → a single linked card

A renderer built this way — call it AutoField — is never written per screen. It's written once, against the types, and it works for any object you point it at. That single fact is the engine of the whole frontend. It's what lets the same ObjectTable show assets in the Client and preview rows in the Builder. And it's what makes the Builder's preview pane literally the same renderer the end user gets — just aimed at an in-progress definition.

Enough words. Here are both apps, sharing one definition. Edit the rulebook on the left; the data app on the right rebuilds itself. Change a property's type and watch its input and its table cell change with it.

Two apps, one shared definition. Open full screen ↗

👩‍💻 Mai: When I switched criticality from a number to an enum, the form turned into a dropdown and I never touched the form code. That's the metadata thing.

👨‍🏫 Hamed: That's the whole game. The form is a function of the definition. The Builder changed one fact about a property's type; the Client read the new definition and re-rendered itself. Nobody hand-wrote "if it's an enum, show a dropdown" for that field — AutoField does it for every field, because it works off the type, not the field name.

One thing the UI does not do: enforce security

A quick but important guardrail, since one of these apps faces customers. Everything from part 1 still holds: security is enforced on the server, on every read and write. The frontend's job is only to reflect it. If a caller may not see a column, the data client never returns it — so the UI simply has nothing to draw. The component kit reads the visibility metadata so it doesn't render a control for a field the server would reject anyway, but that's a courtesy to the user, not a security boundary.

👩‍💻 Mai: So if I hide a column in the Client, that's the security working?

👨‍🏫 Hamed: No — and this is the trap to never fall into. By the time the data reaches your component, the server already removed what the caller can't see. Your hiding is cosmetic; the real gate ran long before. A frontend that enforces permissions is a frontend with a hole in it. Reflect, never enforce.

The one-paragraph summary

A frontend on an ontology is the read and write models from Parts 3 and 5, rendered — there's no new system underneath. But there isn't one app; there are two, split by which plane they edit: a Builder that edits the definitions (the rulebook — types, properties, policies) and a Client that edits the data (the instances). Because they share an ontology, they share one foundation: a typed data client, the runtime metadata contract, and a component kit. The engine that makes that foundation serve both apps is typed-data-renders-itself — because every property has a type the ontology exposes, a component like AutoField can choose the right widget on its own, so the Client renders itself from whatever the Builder defined. And throughout, the UI only ever reflects the server's security; it never enforces it.


Series so far: part 1 the big picture · part 2 reuse & agents · part 3 how you read · part 4 objects, properties, links · part 5 how you change it. Next, the concrete half of the frontend: building a single screen — how the data-layer hooks wrap the read and write models, the component kit by property type, and the choice between a bespoke client and a config-driven one.