Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The quick lookup surface: one line and a minimal snippet per construct. For rule
| [`function`](/spec/entities#function-the-presentation-role) | an explicit presentation role (Document, Setting, ...) |
| [`label`](/spec/entities#label-a-stored-display-name) | a stored, read-only display name for lookups |
| [`label` / `countryLabels` (field)](/spec/entities#label-countrylabels-what-a-field-is-called) | what a field is called, and the variants the tenant's country resolves |
| [`emptyText` (field)](/spec/entities#emptytext-what-an-empty-value-reads-as) | the text a field's empty value reads as wherever it is shown, and its input's placeholder |
| [`number`](/spec/entities#document-numbering) | a platform-numbered, gap-free document field |
| [`checks`](/spec/entities#checks-declarative-validations) | cross-field / cross-line validations |
| [`checks: severity: warn` / `duplicate` / `itemsCompare`](/spec/entities#severity-warn-warn-and-confirm-instead-of-refuse) | a check the person saving confirms instead of a refusal |
Expand Down
39 changes: 39 additions & 0 deletions docs/spec/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fields:
| `unique` | a UNIQUE constraint (e.g. a code or business key) |
| `label` | what the field is called in the UI, replacing the humanized name (see [what a field is called](#label-countrylabels-what-a-field-is-called)) |
| `countryLabels` | label variants resolved from the tenant's country rather than the reader's language (see [what a field is called](#label-countrylabels-what-a-field-is-called)) |
| `emptyText` | the text an empty value reads as wherever the field is shown (see [what an empty value reads as](#emptytext-what-an-empty-value-reads-as)) |
| `visibleTo` | an allow-list of roles that may read the field, enforced where the data leaves the server (see [Role-scoped field visibility](#role-scoped-field-visibility-visibleto)) |
| `precision` / `scale` | override the decimal default (16, 2) |
| `readOnly` | rendered read-only in the UI (e.g. a calculated total) |
Expand Down Expand Up @@ -156,6 +157,44 @@ would leave the base label rendering with nothing reported. A blank `label:`, an
label, MUST be rejected for the same reason.
:::

## emptyText — what an empty value reads as

For many fields an empty value means something: a loan limit with no value is *no limit*, a
membership with no expiry date *never* expires, a nickname nobody chose is *not set*. Shown as a
placeholder mark, or left out of a details block that lists only filled values, the meaning is lost -
a reader cannot tell "no limit" from "not loaded" - and a sentinel value (`-1`, `9999-12-31`) has to
be known by every rule, check and report. State it instead:

```yaml
fields:
- { name: maxLoans, type: integer, readOnly: true, label: Loan limit, emptyText: No limit }
- { name: expiresOn, type: date, label: Membership expires, emptyText: Never }
- { name: nickname, type: string, length: 40, emptyText: Not set }
```

The text is what the record page, its details, the list cells and record sheet, collection panels,
related-records registers and the subtitle or summary line print for an empty value; a details block
always shows the field's row. On the create and edit forms it is the placeholder of the field's
free-text or number input, telling the editor what leaving it empty means. It is written in the
default language, as `label:` is, and translated the same way. Nothing is stored: the data, the REST
answers, exports and prints are unchanged.

::: info Normative
Wherever a conforming generator shows a field's value as a value - a record's page and its details,
a list cell, a list's record sheet, a collection panel or related-records register, a record's
subtitle or summary line - an empty value (none, a blank text, an empty list) of a field declaring
`emptyText: <text>` MUST read as that text. A details block that otherwise shows only filled values
MUST show the row of such a field. The text MUST be the placeholder of the field's free-text or
number input on the create and edit forms, and MUST NOT be stored. It MUST seed the default-language
catalog next to the field's label and resolve from the reader's language catalog, then the declared
text. A field the reader may not see MUST NOT show its text. The text MUST NOT change what is stored,
served, exported or printed.
A blank `emptyText:`, and `emptyText:` on a `boolean`, a `uuid`, the primary key, a `required`
field, a document `number:`, a `placement: hidden` field, an `aggregate` field, the document title or
a chat message body, MUST be rejected when the model is read. A model declaring no `emptyText:` MUST
generate as before.
:::

## unique — a business key over more than one field

`unique: true` on a field constrains one column. When what makes a row unique spans several,
Expand Down