Codegen entity meta-model — Presentation
Table of Contents
This page is a segment of the Codegen org-entity meta-model hub, covering
the ** Presentation drawer: the knobs an entity authors for its UI.
The TypeScript facets that once read them are gone; the surviving
reader is ores.cpp.presentation, which
renders a domain type for display. The
knobs themselves are unchanged. Reading across the facets by name —
:domain_class:, :save_message_type:, etc. below are just pointers
back to types C++ Domain and C++ Protocol already generated —
so this page assumes those two.
1. Physical model mapping
| Facet | Description |
|---|---|
ores.cpp.presentation |
Server-side rendering of a domain type for display. |
2. The one thing to internalise before reading the knob tables
Eight *_convention entities (currency-like lookup entities whose
saves require a change-reason code) were regenerated without
:has_change_reason_cache: true in their presentation drawer. Nothing errored
— codegen ran clean, the build succeeded, the tests passed. The only
symptom was an empty, unusable change-reason dropdown in the save
dialog, found by hand during manual QA weeks later (task
AA97E310, PRs #1571/#1590).
That's the general failure mode for every knob in the Behavioural
group below, not a one-off bug: codegen's templates gate optional
behaviour with a mustache section tag,
{{#domain_entity.<knob>}}, and mustache's rule for section tags
is "false or absent renders as nothing" — there is no way for the
template to tell "author explicitly said no" apart from "author
forgot". An omitted knob is therefore indistinguishable, at
generation time, from a deliberate false; the only way to discover
you got it wrong is a human noticing the missing feature, later, by
hand. Treat every knob below as something to set on purpose, not
something safe to leave out and hope the default is right.
3. Worked example
book, trimmed to the knobs this page documents. book is
party-scoped with a UUID primary key, so it also exercises
parent_entity_singular and has_uuid_primary_key:
** Presentation
,:PROPERTIES:
,:domain_include: ores.refdata.api/domain/book.hpp
,:domain_class: refdata::domain::book
,:protocol_include: ores.refdata.api/messaging/book_protocol.hpp
,:collection_name: books
,:item_var: book
,:key_field: name
,:has_uuid_primary_key: true
,:get_request_class: refdata::messaging::get_books_request
,:get_response_class: refdata::messaging::get_books_response
,:get_message_type: get_books_request
,:save_request_class: refdata::messaging::save_book_request
,:save_response_class: refdata::messaging::save_book_response
,:save_message_type: save_book_request
,:save_request_item_field: book
,:delete_request_class: refdata::messaging::delete_book_request
,:delete_response_class: refdata::messaging::delete_book_response
,:delete_message_type: delete_book_request
,:history_request_class: refdata::messaging::get_book_history_request
,:history_response_class: refdata::messaging::get_book_history_response
,:history_message_type: get_book_history_request
,:has_change_reason_cache: true
,:parent_entity_singular: portfolio
,:settings_group: BookListWindow
,:window_title: Books
,:icon: Book
,:END:
Feature diagram of the Behavioural group below (the non-structural
domain_entity variability; naming/wiring and overrides omitted, they
aren't real branch points):
4. Naming and wiring
This group is the bulk of the drawer, but conceptually the simplest: every property is a pointer to a class, header, or NATS message type that C++ Domain or C++ Protocol already generated. Get one wrong and generation still succeeds — it's just wired to the wrong class, which almost always fails at compile time. That's a much safer failure mode than the silent Behavioural gaps below, which is why this group gets a lighter treatment here.
- Domain and protocol pointers —
:domain_include:,:domain_class:,:protocol_include:,:collection_name:,:item_var:,:key_field:. - Per-verb NATS message wiring, one triple/quad per CRUD verb —
get/list (
:get_request_class:,:get_response_class:,:get_message_type:), save (adds:save_request_item_field:), delete, and history. Each triple names the request struct, response struct, and message-type identifier C++ Protocol generated for that verb. - Cross-client invalidation —
:changed_event_class:,:changed_event_include:point at C++ NATS's eventing facet output, so a client knows which event to listen for to refresh itself. - Window chrome —
:settings_group:,:window_title:,:icon:: cosmetic, but still required per entity (no sensible default exists for a window title).
5. Behavioural
Every property here is, in MASD Variability terms, a feature — and
specifically MASD's non-structural kind: none of them change the
entity's own logical shape, only which generated UI code paths exist
for it. (Contrast Keys and columns's :has_tenant_id:, which does
add a field — structural variability, a different MASD category
entirely.) Every property here is a boolean or identifier that
changes what gets generated, and every one defaults to "off"/absent
if you don't set it — the silent-omission trap described above, which
in MDE terms is this whole group's implicit presence condition (IPC)
being false, not the true default the wider literature usually
recommends as the safer choice.
- Key shape —
:has_uuid_primary_key:(default: false, treated as a text PK). Also drives:key_field_is_uuid:, which wraps the key inboost::uuids::to_string()when:key_field:equals the primary-key column. - Change tracking —
:has_change_reason_cache:(default: false, no change-reason dropdown). Settruewhenever the DB layer has achange_reason_codecolumn — see the worked bug above. - API surface —
:has_explorer_api:(queryable via the generic Explorer API),:has_pagination:(list window paginates instead of loading everything). - Import/export —
:has_csv_xml_io:turns on a whole family of paired properties (:csv_export_class:,:xml_export_class:,:xml_import_class:, and their matching_include=/=_methodproperties) naming the hand-written import/export helper classes; set them together or not at all. - Miscellany —
:has_export_macro:(DLL export macro, Windows builds),:has_version_navigation:(prev/next version controls on the detail dialog). - Parent scoping —
:parent_entity_singular:,:parent_entity_pascal:: set when the entity is only ever listed under a parent window (e.g.bookunderportfolio) rather than having its own top-level entry. :has_readonly_paginated_list:(default: false) — strips every add/edit/delete/history affordance from the generated controller and top-level window: no toolbar actions, no detail-dialog wiring, no version navigation, no history dialog. Reload and pagination are untouched (:has_pagination:is independent and orthogonal). Use for a junction-backed or materialised dataset with nothing to edit (first consumer:calendar_dates, scoped bycalendar_code) — pair it with disabling this entity's detail-dialog archetypes individually via the entity's:ores.*.enabled:drawer overrides, since no detail dialog is wired to when this knob is set.- Parent-scoped lists —
:has_parent_scoped_list:(default: false), paired with:parent_key_field:(the protocol get-request field, e.g.calendar_code) and:parent_key_param:(the C++ member/ parameter name) – both required when the knob is set, since the parent key belongs to a different entity and neither can be derived. Only meaningful alongside:has_readonly_paginated_list:(a still-full-CRUD entity has no analogous need). When set, the deleted templates wired it through three generated classes: the client model's constructor took the parent key and sent it on every paginated fetch alongside offset/limit; the list window threaded it through to the model it created; and the controller exposed anopenForParent()entry point that opened the read-only list filtered by that key. Do not conflate this with:parent_entity_singular:=/:has_parent_relationship:= above, which solves that unrelated problem.
6. Overrides
Both of these have sensible defaults derived from the entity's other knobs — set them only when a specific entity's generated protocol diverges from that default naming convention:
:delete_request_id_field:defaults toidswhen:has_uuid_primary_key:is true, else{pk_column}s. Currency's protocol names this fieldiso_codesinstead, so currency sets it explicitly.:history_response_data_field:defaults tohistory. Protocols predating that naming convention name the field after the entity's plural collection instead — set explicitly when regenerating one of those.
7. Per-field knobs
The deleted templates were the only readers of these knobs; nothing consumes them today. They stay as the model's authored record of the intent, until the TypeScript UI grows the equivalent controls.
The *** Detail fields and *** Columns tables (one row
per entity field, not the whole-entity drawer above) carry their own
per-field properties, layered on top of the plain field=/=label=/
=type columns every field needs:
badge_key— instead of rendering this field's value as plain text, render it as a coloured badge, resolved via ores.dq'scode_domain=/=badge_definitionlookup. Applies in both the detail dialog and the list column. Omit only when the field genuinely has no DQ badge mapping — most fields.flag_source,combo_*— dropdown/combo-box wiring for a soft-FK field, so the detail dialog offers a picker instead of a free-text box. The full property set (fetch function, watcher name, sort/tooltip fields, …) isn't enumerated on this page yet; see an existing entity with a combo field, e.g.book'sbook_statuscolumn, for the complete worked shape. (Gap: this needs its own worked example on this page — flag for follow-up.)tooltip_field— list-column only. When set, the generatedClient<Entity>Model::data()returns the value of the named field as the tooltip on this column's cells. Used for columns whose displayed text is a human-friendly stand-in for the real identifier (e.g. the hostDisplayNamecolumn tooltips theexternal_id). Plain-field access only; nofield_accessnesting.data_paste— list-column only. When set to a paste-block kind UUID, the generateddata()case for this column emits the pasted body inside acase <EnumName>: { ... }block instead of the plain type-drivenreturn. The body is pasted verbatim (a<<paste:UUID>>marker is emitted per column and resolved by the standard paste mechanism, so the block must be declared in the same model with:implements: <that UUID>). Use for display logic a single field cannot express — fallbacks, empty-value placeholders, lookups. The{{item_var}}name (e.g.host) is in scope inside the body.
8. See also
- Codegen org-entity meta-model — the hub.
- C++ Domain / C++ Protocol — the types this facet's Naming/wiring knobs point at.
- C++ NATS — the changed-event the UI listens for to invalidate its client-side cache.
- Variability — MASD's formal vocabulary for what this page's Behavioural/Overrides knobs are.