@simai-studio/ui 5.20261023.126 → 5.20261023.212

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +4 -4
  2. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-field-hidden-by-its-condition-is-not-saved.json +45 -0
  3. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-field-says-what-it-falls-back-to.json +12 -0
  4. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-field-taken-out-of-an-area-is-unplaced.json +40 -0
  5. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-reader-is-offered-nothing-to-change.json +23 -0
  6. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-related-group-is-a-list-not-fields.json +23 -0
  7. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/a-stale-confirmation-is-asked-again.json +39 -0
  8. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/conditions-arrive-declared.json +82 -0
  9. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/create-applied-answers-with-the-id.json +24 -0
  10. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/create-field-errors-are-refused.json +22 -0
  11. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/field-presentation-keys-are-declared.json +12 -0
  12. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/group-save-applied-is-one-revision.json +16 -0
  13. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/group-save-confirmed-with-the-digest-is-applied.json +35 -0
  14. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/group-save-field-errors-are-refused.json +16 -0
  15. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/group-save-may-ask-for-a-confirmation.json +38 -0
  16. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/group-save-stale-revision-is-a-conflict.json +16 -0
  17. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/load-applied-carries-the-object.json +12 -0
  18. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/load-for-create-is-an-empty-object.json +24 -0
  19. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/load-late-answer-is-dropped.json +15 -0
  20. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/load-refused-is-explained.json +12 -0
  21. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/load-unavailable-is-honest.json +12 -0
  22. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/lookup-arrives-with-its-label.json +70 -0
  23. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/opening-a-reference-is-the-hosts-decision.json +12 -0
  24. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/opening-the-record-as-a-page-is-offered-only-where-it-leads-somewhere.json +28 -0
  25. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-carry-the-source-and-a-limit.json +33 -0
  26. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-page-carries-a-cursor.json +15 -0
  27. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-refused-is-explained.json +14 -0
  28. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-search-by-source-reaches-the-host.json +30 -0
  29. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-search-reaches-the-host.json +14 -0
  30. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/options-without-the-capability.json +14 -0
  31. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/rare-fields-arrive-declared.json +12 -0
  32. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/removing-a-file-is-a-save.json +13 -0
  33. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/save-applied-is-one-revision.json +26 -0
  34. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/save-may-ask-for-a-confirmation.json +36 -0
  35. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/save-stale-revision-is-a-conflict.json +23 -0
  36. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/the-address-is-the-hosts.json +12 -0
  37. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/upload-answers-with-the-stored-file.json +14 -0
  38. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/upload-refused-is-explained.json +14 -0
  39. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-create-answers-with-the-record-through-the-new-view.json +32 -0
  40. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-create-shared-may-be-refused.json +27 -0
  41. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-delete-applied-answers-with-what-is-left.json +25 -0
  42. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-delete-the-last-shared-one-is-refused.json +25 -0
  43. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-profile-applied-carries-the-record-through-it.json +25 -0
  44. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-profile-refused-is-explained.json +25 -0
  45. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-profile-unavailable-without-the-capability.json +23 -0
  46. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-rename-changes-the-label-only.json +26 -0
  47. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-reset-answers-with-what-now-resolves.json +30 -0
  48. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-save-against-a-stale-revision-is-a-conflict.json +36 -0
  49. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-save-is-one-layer-one-revision.json +35 -0
  50. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-save-may-name-an-area-the-record-does-not-carry.json +49 -0
  51. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-save-rejects-entries-by-kind.json +36 -0
  52. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/cases/view-settings-without-the-capability.json +30 -0
  53. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/kit/manifest.json +66 -0
  54. package/distr/core/contracts/ports/simai.property-view-port/1.14.0/port.json +2484 -0
  55. package/distr/smart/field-renderers/js/field-renderers.min.js +128 -87
  56. package/package-manifest.json +57 -4
  57. package/package.json +5 -4
@@ -0,0 +1,2484 @@
1
+ {
2
+ "schema": "simai.composite.host-port.v1",
3
+ "port": "simai.property-view-port",
4
+ "version": "1.14.0",
5
+ "component": "smart.property-view",
6
+ "element": "sf-property-view",
7
+ "summary": "The host seam for one object's fields shown as sections, groups and fields. The host owns the values, the arrangement, the revision, the address, the transfer of files and what opening a reference means; a group is saved as one revision by the button. 1.10.0 adds named views: a flat list a person chooses from, each one a whole arrangement with a permanent id, with the layers resolving inside the chosen one, and the three acts that keep that list: make, rename, remove. Since 1.11.0 the answer is described here: one object whose fields sit beside `answer`, not under `data` as in the data view port. 1.12.0 puts the record's rights and its creation in the port: `actions` names `record.edit` (the fields may be changed) beside the lifecycle acts, `record.load` with `mode: create` answers an empty object of the collection laid out by its arrangement, `record.create` makes it, and `record.save` saves the whole object as one revision, for a surface where every group is open at once. 1.13.0 makes the chosen view the person's own: the host keeps it per collection and answers record.load and record.open through it, so a reload draws the view the person chose. 1.14.0 adds what a form needs beyond plain values: a `lookup` field, a value chosen from a reference list the host keeps and names by `source`, paged and searched by the host through field.options (which now carries the `source` and a `limit`); a save the host will make only once confirmed, refused with reason `confirm_required` and a `confirm` to show, and raised again with its digest, as the data view port has done since 1.8.0; and `when` on a field, the conditions under which it is shown, required and enabled, evaluated by the component over the values on screen.",
8
+ "capabilities": {
9
+ "field-options": {
10
+ "summary": "Candidate records for a reference field, a page at a time with a search. A host without this capability is not broken: the field shows its current value read-only, which is what the component does when no editor can be built.",
11
+ "intents": [
12
+ "field.options"
13
+ ],
14
+ "data": [
15
+ "options_page"
16
+ ]
17
+ },
18
+ "field-upload": {
19
+ "summary": "Taking a file the person chose. A host without this capability is not broken: a file field shows what it already holds, read-only, which is the same arrangement as field-options.",
20
+ "intents": [
21
+ "field.upload"
22
+ ],
23
+ "data": []
24
+ },
25
+ "view-settings": {
26
+ "summary": "Configuring the arrangement: saving a layer and returning it to what lies beneath. Without it the component offers no way to configure the view, and shows no control that would lead to one. The arrangement rights arrive inside the record answer, as its `view` block, rather than as a data shape of their own -- which is why this capability names no data.",
27
+ "intents": [
28
+ "view.create",
29
+ "view.delete",
30
+ "view.profile",
31
+ "view.rename",
32
+ "view.reset",
33
+ "view.save"
34
+ ]
35
+ },
36
+ "record-open": {
37
+ "summary": "Opening the record as a page of its own, away from the panel. A host without this capability is not broken: the panel is the whole of the record, and the control that would lead elsewhere is not drawn. It is a capability rather than a consequence of the address, because a host may well have an address to copy and no page to open -- which is how a control that did nothing came to stand in the panel's own strip.",
38
+ "intents": [
39
+ "record.open"
40
+ ]
41
+ }
42
+ },
43
+ "data": {
44
+ "record": {
45
+ "summary": "The object as the host sees it now, with its arrangement. The component holds this for rendering and never writes to it.",
46
+ "required": true,
47
+ "schema": {
48
+ "type": "object",
49
+ "required": [
50
+ "sections"
51
+ ],
52
+ "additionalProperties": false,
53
+ "properties": {
54
+ "record_id": {
55
+ "type": [
56
+ "string",
57
+ "integer",
58
+ "null"
59
+ ]
60
+ },
61
+ "collection": {
62
+ "type": [
63
+ "string",
64
+ "null"
65
+ ]
66
+ },
67
+ "revision": {
68
+ "type": [
69
+ "string",
70
+ "integer",
71
+ "null"
72
+ ],
73
+ "description": "The revision a later write will be compared against. Reading does not require one; writing in a later port version does."
74
+ },
75
+ "actions": {
76
+ "type": "array",
77
+ "uniqueItems": true,
78
+ "items": {
79
+ "type": "string",
80
+ "minLength": 1
81
+ },
82
+ "description": "What this person may do with the record itself, named the way a data view names a row's actions. `record.edit`: the fields may be changed (since 1.12.0; a host on an earlier version never sent it, and for it a group is editable unless it says otherwise). `record.delete`, `record.archive`, `record.restore`: the lifecycle acts. The component offers only what is named here: a control for something the host would refuse is a control that exists to fail, so a reader -- no `record.edit`, no lifecycle act -- is shown the values and nothing to change them with."
83
+ },
84
+ "unplaced": {
85
+ "type": "array",
86
+ "description": "Fields the collection has that this view does not place in any group. They are not hidden -- hiding keeps a field in its group -- they are out of the arrangement, and while the view is being arranged a person drags them in and out. A host that does not offer this sends nothing.",
87
+ "items": {
88
+ "type": "object",
89
+ "required": [
90
+ "key",
91
+ "propertyType"
92
+ ],
93
+ "additionalProperties": false,
94
+ "properties": {
95
+ "key": {
96
+ "type": "string",
97
+ "minLength": 1
98
+ },
99
+ "label": {
100
+ "type": "string"
101
+ },
102
+ "propertyType": {
103
+ "type": "string",
104
+ "description": "The Larena property type the renderer set is addressed by. The host supplies the type; the Framework supplies the template. Since 1.14.0 `lookup`: a value chosen from a reference list, {value, label}, or a list of them when `multiple`."
105
+ },
106
+ "variant": {
107
+ "type": [
108
+ "string",
109
+ "null"
110
+ ]
111
+ },
112
+ "value": {},
113
+ "unit": {
114
+ "type": [
115
+ "string",
116
+ "null"
117
+ ]
118
+ },
119
+ "width": {
120
+ "enum": [
121
+ "half",
122
+ "full"
123
+ ]
124
+ },
125
+ "required": {
126
+ "type": "boolean"
127
+ },
128
+ "rare": {
129
+ "type": "boolean"
130
+ },
131
+ "hidden": {
132
+ "type": "boolean"
133
+ },
134
+ "inherited": {
135
+ "type": "boolean"
136
+ },
137
+ "hint": {
138
+ "type": [
139
+ "string",
140
+ "null"
141
+ ]
142
+ },
143
+ "options": {
144
+ "type": [
145
+ "array",
146
+ "null"
147
+ ],
148
+ "description": "The choices of a choice, a list of choices or a lookup, each {value, label}. For a lookup it is the whole list, for a list short enough to send: the person chooses from it in place and field.options is never asked. A longer list is the host's to page, from `source`."
149
+ },
150
+ "trueText": {
151
+ "type": [
152
+ "string",
153
+ "null"
154
+ ]
155
+ },
156
+ "falseText": {
157
+ "type": [
158
+ "string",
159
+ "null"
160
+ ]
161
+ },
162
+ "readonly": {
163
+ "type": "boolean",
164
+ "description": "The host forbids editing this field. The control renders disabled rather than absent, so a person sees the value and sees that it is not theirs to change."
165
+ },
166
+ "multiple": {
167
+ "type": "boolean",
168
+ "description": "This field holds more than one value. Declared by the host, because an array arriving in value is also what a single value that happens to be a list looks like."
169
+ },
170
+ "placeholder": {
171
+ "type": [
172
+ "string",
173
+ "null"
174
+ ],
175
+ "description": "What an empty control reads before a person acts."
176
+ },
177
+ "searchPlaceholder": {
178
+ "type": [
179
+ "string",
180
+ "null"
181
+ ],
182
+ "description": "What the search box inside a record picker reads."
183
+ },
184
+ "badgeAppearance": {
185
+ "type": [
186
+ "string",
187
+ "null"
188
+ ],
189
+ "description": "How a value shown as a badge is coloured. The host knows which values mean trouble; the component has no opinion about the meaning of a code."
190
+ },
191
+ "uploadText": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "description": "The call to action in a file field. Absent, the file control shows its own default, which names image formats and is wrong for a field that takes any file."
197
+ },
198
+ "uploadHint": {
199
+ "type": [
200
+ "string",
201
+ "null"
202
+ ],
203
+ "description": "The line under the call to action in a file field."
204
+ },
205
+ "uploadSupportingText": {
206
+ "type": [
207
+ "string",
208
+ "null"
209
+ ],
210
+ "description": "The quiet line under the file field, for a size or a count limit."
211
+ },
212
+ "formats": {
213
+ "type": [
214
+ "string",
215
+ "null"
216
+ ],
217
+ "description": "What the field accepts, in words a person reads. Shown as written; never parsed."
218
+ },
219
+ "accept": {
220
+ "type": [
221
+ "string",
222
+ "null"
223
+ ],
224
+ "description": "What the field accepts, as the file dialog filters it. Separate from formats, because the words a person reads and the filter a dialog applies are not the same string and the host owns both."
225
+ },
226
+ "labels": {
227
+ "type": [
228
+ "object",
229
+ "null"
230
+ ],
231
+ "additionalProperties": false,
232
+ "properties": {
233
+ "true": {
234
+ "type": [
235
+ "string",
236
+ "null"
237
+ ]
238
+ },
239
+ "false": {
240
+ "type": [
241
+ "string",
242
+ "null"
243
+ ]
244
+ }
245
+ },
246
+ "description": "The two words a boolean reads, as an alternative shape to trueText and falseText. Both are accepted because both already arrive; trueText wins when a host sends both."
247
+ },
248
+ "constraints": {
249
+ "type": [
250
+ "object",
251
+ "null"
252
+ ],
253
+ "properties": {
254
+ "options": {
255
+ "type": [
256
+ "array",
257
+ "null"
258
+ ]
259
+ }
260
+ },
261
+ "description": "Where a host carries the allowed values when it does not put them in options. Read only for its options; the rest belongs to the host and is not interpreted."
262
+ },
263
+ "hiddenBy": {
264
+ "type": [
265
+ "string",
266
+ "null"
267
+ ],
268
+ "enum": [
269
+ "package",
270
+ "shared",
271
+ "personal",
272
+ null
273
+ ],
274
+ "description": "The layer that hid this entry, when something did. The component disables un-hiding it from a layer below that one, so the rule \"hidden for everyone cannot be shown again in a personal view\" is a fact the host states per entry rather than a rule the component reimplements -- which would put the permission model in two places and let them drift."
275
+ },
276
+ "variantOptions": {
277
+ "type": [
278
+ "array",
279
+ "null"
280
+ ],
281
+ "description": "The template variants this field may be shown with, as the host offers them. The component shows the chooser only for what is listed, because inventing a variant a host cannot render would offer a person a broken view."
282
+ },
283
+ "inheritedValue": {
284
+ "type": [
285
+ "string",
286
+ "null"
287
+ ],
288
+ "description": "What this field falls back to while it holds nothing, as the host resolves it -- a colour from the theme, a default from a package. Shown as inherited rather than drawn as the value: a view that drew it as the value would make «not set» and «set to the same thing» look identical, and only one of them follows the source when the source changes. `inherited` already says *that* a value is inherited; this says *what* it is."
289
+ },
290
+ "swatches": {
291
+ "type": [
292
+ "string",
293
+ "null"
294
+ ],
295
+ "description": "The values a product already has for this field, as the host words them -- for a colour, a space or comma separated list of #hex or #hex|Name. A person picks the brand colour rather than matching it by eye. Offered, never enforced: a field with swatches still takes any value its type allows."
296
+ },
297
+ "language": {
298
+ "type": [
299
+ "string",
300
+ "null"
301
+ ],
302
+ "description": "For a field whose value is code or markup, which language it is, so it can be shown as code rather than as a paragraph. The host names it; the component does not guess from the text."
303
+ },
304
+ "source": {
305
+ "type": [
306
+ "string",
307
+ "null"
308
+ ],
309
+ "description": "Since 1.14.0. The host's id of the reference list a lookup chooses from -- a dictionary, a directory, another collection. Opaque to the component: it is sent back in field.options as it arrived, so one host answers every lookup on the object from one place."
310
+ },
311
+ "when": {
312
+ "type": "object",
313
+ "additionalProperties": false,
314
+ "description": "Since 1.14.0. When the field is shown, required and enabled, each a condition over the other fields' values on screen, evaluated by the component as the person edits. A key that is absent leaves that part as declared. A field hidden by its condition is not drawn and is not sent in a save, and it is not cleared: it keeps the value it holds, inherited or not, because hidden by a condition is not the same as emptied. `required` decides the required mark and the control's own required state; the host validates as it always did. `enabled: false` draws the control disabled, as `readonly` does.",
315
+ "properties": {
316
+ "visible": {
317
+ "type": "object",
318
+ "description": "When the field is shown, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
319
+ "oneOf": [
320
+ {
321
+ "type": "object",
322
+ "required": [
323
+ "field",
324
+ "op"
325
+ ],
326
+ "additionalProperties": false,
327
+ "properties": {
328
+ "field": {
329
+ "type": "string",
330
+ "minLength": 1,
331
+ "description": "The key of the field compared."
332
+ },
333
+ "op": {
334
+ "enum": [
335
+ "eq",
336
+ "ne",
337
+ "in",
338
+ "not_in",
339
+ "empty",
340
+ "not_empty"
341
+ ]
342
+ },
343
+ "value": {
344
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
345
+ }
346
+ }
347
+ },
348
+ {
349
+ "type": "object",
350
+ "required": [
351
+ "all"
352
+ ],
353
+ "additionalProperties": false,
354
+ "properties": {
355
+ "all": {
356
+ "type": "array",
357
+ "minItems": 1,
358
+ "items": {
359
+ "type": "object",
360
+ "description": "A condition of this grammar."
361
+ }
362
+ }
363
+ }
364
+ },
365
+ {
366
+ "type": "object",
367
+ "required": [
368
+ "any"
369
+ ],
370
+ "additionalProperties": false,
371
+ "properties": {
372
+ "any": {
373
+ "type": "array",
374
+ "minItems": 1,
375
+ "items": {
376
+ "type": "object",
377
+ "description": "A condition of this grammar."
378
+ }
379
+ }
380
+ }
381
+ },
382
+ {
383
+ "type": "object",
384
+ "required": [
385
+ "not"
386
+ ],
387
+ "additionalProperties": false,
388
+ "properties": {
389
+ "not": {
390
+ "type": "object",
391
+ "description": "A condition of this grammar."
392
+ }
393
+ }
394
+ }
395
+ ]
396
+ },
397
+ "required": {
398
+ "type": "object",
399
+ "description": "When the field is required, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
400
+ "oneOf": [
401
+ {
402
+ "type": "object",
403
+ "required": [
404
+ "field",
405
+ "op"
406
+ ],
407
+ "additionalProperties": false,
408
+ "properties": {
409
+ "field": {
410
+ "type": "string",
411
+ "minLength": 1,
412
+ "description": "The key of the field compared."
413
+ },
414
+ "op": {
415
+ "enum": [
416
+ "eq",
417
+ "ne",
418
+ "in",
419
+ "not_in",
420
+ "empty",
421
+ "not_empty"
422
+ ]
423
+ },
424
+ "value": {
425
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
426
+ }
427
+ }
428
+ },
429
+ {
430
+ "type": "object",
431
+ "required": [
432
+ "all"
433
+ ],
434
+ "additionalProperties": false,
435
+ "properties": {
436
+ "all": {
437
+ "type": "array",
438
+ "minItems": 1,
439
+ "items": {
440
+ "type": "object",
441
+ "description": "A condition of this grammar."
442
+ }
443
+ }
444
+ }
445
+ },
446
+ {
447
+ "type": "object",
448
+ "required": [
449
+ "any"
450
+ ],
451
+ "additionalProperties": false,
452
+ "properties": {
453
+ "any": {
454
+ "type": "array",
455
+ "minItems": 1,
456
+ "items": {
457
+ "type": "object",
458
+ "description": "A condition of this grammar."
459
+ }
460
+ }
461
+ }
462
+ },
463
+ {
464
+ "type": "object",
465
+ "required": [
466
+ "not"
467
+ ],
468
+ "additionalProperties": false,
469
+ "properties": {
470
+ "not": {
471
+ "type": "object",
472
+ "description": "A condition of this grammar."
473
+ }
474
+ }
475
+ }
476
+ ]
477
+ },
478
+ "enabled": {
479
+ "type": "object",
480
+ "description": "When the field is enabled, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
481
+ "oneOf": [
482
+ {
483
+ "type": "object",
484
+ "required": [
485
+ "field",
486
+ "op"
487
+ ],
488
+ "additionalProperties": false,
489
+ "properties": {
490
+ "field": {
491
+ "type": "string",
492
+ "minLength": 1,
493
+ "description": "The key of the field compared."
494
+ },
495
+ "op": {
496
+ "enum": [
497
+ "eq",
498
+ "ne",
499
+ "in",
500
+ "not_in",
501
+ "empty",
502
+ "not_empty"
503
+ ]
504
+ },
505
+ "value": {
506
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
507
+ }
508
+ }
509
+ },
510
+ {
511
+ "type": "object",
512
+ "required": [
513
+ "all"
514
+ ],
515
+ "additionalProperties": false,
516
+ "properties": {
517
+ "all": {
518
+ "type": "array",
519
+ "minItems": 1,
520
+ "items": {
521
+ "type": "object",
522
+ "description": "A condition of this grammar."
523
+ }
524
+ }
525
+ }
526
+ },
527
+ {
528
+ "type": "object",
529
+ "required": [
530
+ "any"
531
+ ],
532
+ "additionalProperties": false,
533
+ "properties": {
534
+ "any": {
535
+ "type": "array",
536
+ "minItems": 1,
537
+ "items": {
538
+ "type": "object",
539
+ "description": "A condition of this grammar."
540
+ }
541
+ }
542
+ }
543
+ },
544
+ {
545
+ "type": "object",
546
+ "required": [
547
+ "not"
548
+ ],
549
+ "additionalProperties": false,
550
+ "properties": {
551
+ "not": {
552
+ "type": "object",
553
+ "description": "A condition of this grammar."
554
+ }
555
+ }
556
+ }
557
+ ]
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
563
+ },
564
+ "sections": {
565
+ "type": "array",
566
+ "items": {
567
+ "type": "object",
568
+ "required": [
569
+ "key",
570
+ "groups"
571
+ ],
572
+ "additionalProperties": false,
573
+ "properties": {
574
+ "key": {
575
+ "type": "string",
576
+ "minLength": 1
577
+ },
578
+ "label": {
579
+ "type": "string"
580
+ },
581
+ "icon": {
582
+ "type": "string",
583
+ "description": "A glyph name for the section in the list. The host names it, because which icon stands for a section is a product's decision."
584
+ },
585
+ "rare": {
586
+ "type": "boolean"
587
+ },
588
+ "hidden": {
589
+ "type": "boolean"
590
+ },
591
+ "groups": {
592
+ "type": "array",
593
+ "items": {
594
+ "type": "object",
595
+ "required": [
596
+ "key",
597
+ "fields"
598
+ ],
599
+ "additionalProperties": false,
600
+ "properties": {
601
+ "key": {
602
+ "type": "string",
603
+ "minLength": 1
604
+ },
605
+ "label": {
606
+ "type": "string"
607
+ },
608
+ "kind": {
609
+ "enum": [
610
+ "fields",
611
+ "related"
612
+ ],
613
+ "description": "What this group holds. «fields» is the default and what every group was before this version, so an answer that says nothing is unchanged. «related» is a list of other records belonging to this one: the component composes a data view in the group instead of fields, and that view asks for its own host port rather than being handed one through this seam -- the records, their columns and their paging are the data-view port is business, and routing them through this port would make a property view a relay for a vocabulary it does not speak."
614
+ },
615
+ "rowCap": {
616
+ "type": [
617
+ "integer",
618
+ "null"
619
+ ],
620
+ "minimum": 0,
621
+ "description": "For a related group: how many rows to show before offering to open the whole list. Null or absent leaves the component default. The cap is presentation -- the host paging is unaffected, and the list asks its own host for records as it always would."
622
+ },
623
+ "rare": {
624
+ "type": "boolean"
625
+ },
626
+ "hidden": {
627
+ "type": "boolean"
628
+ },
629
+ "columns": {
630
+ "enum": [
631
+ 1,
632
+ 2
633
+ ]
634
+ },
635
+ "fields": {
636
+ "type": "array",
637
+ "items": {
638
+ "type": "object",
639
+ "required": [
640
+ "key",
641
+ "propertyType"
642
+ ],
643
+ "additionalProperties": false,
644
+ "properties": {
645
+ "key": {
646
+ "type": "string",
647
+ "minLength": 1
648
+ },
649
+ "label": {
650
+ "type": "string"
651
+ },
652
+ "propertyType": {
653
+ "type": "string",
654
+ "description": "The Larena property type the renderer set is addressed by. The host supplies the type; the Framework supplies the template. Since 1.14.0 `lookup`: a value chosen from a reference list, {value, label}, or a list of them when `multiple`."
655
+ },
656
+ "variant": {
657
+ "type": [
658
+ "string",
659
+ "null"
660
+ ]
661
+ },
662
+ "value": {},
663
+ "unit": {
664
+ "type": [
665
+ "string",
666
+ "null"
667
+ ]
668
+ },
669
+ "width": {
670
+ "enum": [
671
+ "half",
672
+ "full"
673
+ ]
674
+ },
675
+ "required": {
676
+ "type": "boolean"
677
+ },
678
+ "rare": {
679
+ "type": "boolean"
680
+ },
681
+ "hidden": {
682
+ "type": "boolean"
683
+ },
684
+ "inherited": {
685
+ "type": "boolean"
686
+ },
687
+ "hint": {
688
+ "type": [
689
+ "string",
690
+ "null"
691
+ ]
692
+ },
693
+ "options": {
694
+ "type": [
695
+ "array",
696
+ "null"
697
+ ],
698
+ "description": "The choices of a choice, a list of choices or a lookup, each {value, label}. For a lookup it is the whole list, for a list short enough to send: the person chooses from it in place and field.options is never asked. A longer list is the host's to page, from `source`."
699
+ },
700
+ "trueText": {
701
+ "type": [
702
+ "string",
703
+ "null"
704
+ ]
705
+ },
706
+ "falseText": {
707
+ "type": [
708
+ "string",
709
+ "null"
710
+ ]
711
+ },
712
+ "readonly": {
713
+ "type": "boolean",
714
+ "description": "The host forbids editing this field. The control renders disabled rather than absent, so a person sees the value and sees that it is not theirs to change."
715
+ },
716
+ "multiple": {
717
+ "type": "boolean",
718
+ "description": "This field holds more than one value. Declared by the host, because an array arriving in value is also what a single value that happens to be a list looks like."
719
+ },
720
+ "placeholder": {
721
+ "type": [
722
+ "string",
723
+ "null"
724
+ ],
725
+ "description": "What an empty control reads before a person acts."
726
+ },
727
+ "searchPlaceholder": {
728
+ "type": [
729
+ "string",
730
+ "null"
731
+ ],
732
+ "description": "What the search box inside a record picker reads."
733
+ },
734
+ "badgeAppearance": {
735
+ "type": [
736
+ "string",
737
+ "null"
738
+ ],
739
+ "description": "How a value shown as a badge is coloured. The host knows which values mean trouble; the component has no opinion about the meaning of a code."
740
+ },
741
+ "uploadText": {
742
+ "type": [
743
+ "string",
744
+ "null"
745
+ ],
746
+ "description": "The call to action in a file field. Absent, the file control shows its own default, which names image formats and is wrong for a field that takes any file."
747
+ },
748
+ "uploadHint": {
749
+ "type": [
750
+ "string",
751
+ "null"
752
+ ],
753
+ "description": "The line under the call to action in a file field."
754
+ },
755
+ "uploadSupportingText": {
756
+ "type": [
757
+ "string",
758
+ "null"
759
+ ],
760
+ "description": "The quiet line under the file field, for a size or a count limit."
761
+ },
762
+ "formats": {
763
+ "type": [
764
+ "string",
765
+ "null"
766
+ ],
767
+ "description": "What the field accepts, in words a person reads. Shown as written; never parsed."
768
+ },
769
+ "accept": {
770
+ "type": [
771
+ "string",
772
+ "null"
773
+ ],
774
+ "description": "What the field accepts, as the file dialog filters it. Separate from formats, because the words a person reads and the filter a dialog applies are not the same string and the host owns both."
775
+ },
776
+ "labels": {
777
+ "type": [
778
+ "object",
779
+ "null"
780
+ ],
781
+ "additionalProperties": false,
782
+ "properties": {
783
+ "true": {
784
+ "type": [
785
+ "string",
786
+ "null"
787
+ ]
788
+ },
789
+ "false": {
790
+ "type": [
791
+ "string",
792
+ "null"
793
+ ]
794
+ }
795
+ },
796
+ "description": "The two words a boolean reads, as an alternative shape to trueText and falseText. Both are accepted because both already arrive; trueText wins when a host sends both."
797
+ },
798
+ "constraints": {
799
+ "type": [
800
+ "object",
801
+ "null"
802
+ ],
803
+ "properties": {
804
+ "options": {
805
+ "type": [
806
+ "array",
807
+ "null"
808
+ ]
809
+ }
810
+ },
811
+ "description": "Where a host carries the allowed values when it does not put them in options. Read only for its options; the rest belongs to the host and is not interpreted."
812
+ },
813
+ "hiddenBy": {
814
+ "type": [
815
+ "string",
816
+ "null"
817
+ ],
818
+ "enum": [
819
+ "package",
820
+ "shared",
821
+ "personal",
822
+ null
823
+ ],
824
+ "description": "The layer that hid this entry, when something did. The component disables un-hiding it from a layer below that one, so the rule \"hidden for everyone cannot be shown again in a personal view\" is a fact the host states per entry rather than a rule the component reimplements -- which would put the permission model in two places and let them drift."
825
+ },
826
+ "variantOptions": {
827
+ "type": [
828
+ "array",
829
+ "null"
830
+ ],
831
+ "description": "The template variants this field may be shown with, as the host offers them. The component shows the chooser only for what is listed, because inventing a variant a host cannot render would offer a person a broken view."
832
+ },
833
+ "inheritedValue": {
834
+ "type": [
835
+ "string",
836
+ "null"
837
+ ],
838
+ "description": "What this field falls back to while it holds nothing, as the host resolves it -- a colour from the theme, a default from a package. Shown as inherited rather than drawn as the value: a view that drew it as the value would make «not set» and «set to the same thing» look identical, and only one of them follows the source when the source changes. `inherited` already says *that* a value is inherited; this says *what* it is."
839
+ },
840
+ "swatches": {
841
+ "type": [
842
+ "string",
843
+ "null"
844
+ ],
845
+ "description": "The values a product already has for this field, as the host words them -- for a colour, a space or comma separated list of #hex or #hex|Name. A person picks the brand colour rather than matching it by eye. Offered, never enforced: a field with swatches still takes any value its type allows."
846
+ },
847
+ "language": {
848
+ "type": [
849
+ "string",
850
+ "null"
851
+ ],
852
+ "description": "For a field whose value is code or markup, which language it is, so it can be shown as code rather than as a paragraph. The host names it; the component does not guess from the text."
853
+ },
854
+ "source": {
855
+ "type": [
856
+ "string",
857
+ "null"
858
+ ],
859
+ "description": "Since 1.14.0. The host's id of the reference list a lookup chooses from -- a dictionary, a directory, another collection. Opaque to the component: it is sent back in field.options as it arrived, so one host answers every lookup on the object from one place."
860
+ },
861
+ "when": {
862
+ "type": "object",
863
+ "additionalProperties": false,
864
+ "description": "Since 1.14.0. When the field is shown, required and enabled, each a condition over the other fields' values on screen, evaluated by the component as the person edits. A key that is absent leaves that part as declared. A field hidden by its condition is not drawn and is not sent in a save, and it is not cleared: it keeps the value it holds, inherited or not, because hidden by a condition is not the same as emptied. `required` decides the required mark and the control's own required state; the host validates as it always did. `enabled: false` draws the control disabled, as `readonly` does.",
865
+ "properties": {
866
+ "visible": {
867
+ "type": "object",
868
+ "description": "When the field is shown, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
869
+ "oneOf": [
870
+ {
871
+ "type": "object",
872
+ "required": [
873
+ "field",
874
+ "op"
875
+ ],
876
+ "additionalProperties": false,
877
+ "properties": {
878
+ "field": {
879
+ "type": "string",
880
+ "minLength": 1,
881
+ "description": "The key of the field compared."
882
+ },
883
+ "op": {
884
+ "enum": [
885
+ "eq",
886
+ "ne",
887
+ "in",
888
+ "not_in",
889
+ "empty",
890
+ "not_empty"
891
+ ]
892
+ },
893
+ "value": {
894
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
895
+ }
896
+ }
897
+ },
898
+ {
899
+ "type": "object",
900
+ "required": [
901
+ "all"
902
+ ],
903
+ "additionalProperties": false,
904
+ "properties": {
905
+ "all": {
906
+ "type": "array",
907
+ "minItems": 1,
908
+ "items": {
909
+ "type": "object",
910
+ "description": "A condition of this grammar."
911
+ }
912
+ }
913
+ }
914
+ },
915
+ {
916
+ "type": "object",
917
+ "required": [
918
+ "any"
919
+ ],
920
+ "additionalProperties": false,
921
+ "properties": {
922
+ "any": {
923
+ "type": "array",
924
+ "minItems": 1,
925
+ "items": {
926
+ "type": "object",
927
+ "description": "A condition of this grammar."
928
+ }
929
+ }
930
+ }
931
+ },
932
+ {
933
+ "type": "object",
934
+ "required": [
935
+ "not"
936
+ ],
937
+ "additionalProperties": false,
938
+ "properties": {
939
+ "not": {
940
+ "type": "object",
941
+ "description": "A condition of this grammar."
942
+ }
943
+ }
944
+ }
945
+ ]
946
+ },
947
+ "required": {
948
+ "type": "object",
949
+ "description": "When the field is required, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
950
+ "oneOf": [
951
+ {
952
+ "type": "object",
953
+ "required": [
954
+ "field",
955
+ "op"
956
+ ],
957
+ "additionalProperties": false,
958
+ "properties": {
959
+ "field": {
960
+ "type": "string",
961
+ "minLength": 1,
962
+ "description": "The key of the field compared."
963
+ },
964
+ "op": {
965
+ "enum": [
966
+ "eq",
967
+ "ne",
968
+ "in",
969
+ "not_in",
970
+ "empty",
971
+ "not_empty"
972
+ ]
973
+ },
974
+ "value": {
975
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
976
+ }
977
+ }
978
+ },
979
+ {
980
+ "type": "object",
981
+ "required": [
982
+ "all"
983
+ ],
984
+ "additionalProperties": false,
985
+ "properties": {
986
+ "all": {
987
+ "type": "array",
988
+ "minItems": 1,
989
+ "items": {
990
+ "type": "object",
991
+ "description": "A condition of this grammar."
992
+ }
993
+ }
994
+ }
995
+ },
996
+ {
997
+ "type": "object",
998
+ "required": [
999
+ "any"
1000
+ ],
1001
+ "additionalProperties": false,
1002
+ "properties": {
1003
+ "any": {
1004
+ "type": "array",
1005
+ "minItems": 1,
1006
+ "items": {
1007
+ "type": "object",
1008
+ "description": "A condition of this grammar."
1009
+ }
1010
+ }
1011
+ }
1012
+ },
1013
+ {
1014
+ "type": "object",
1015
+ "required": [
1016
+ "not"
1017
+ ],
1018
+ "additionalProperties": false,
1019
+ "properties": {
1020
+ "not": {
1021
+ "type": "object",
1022
+ "description": "A condition of this grammar."
1023
+ }
1024
+ }
1025
+ }
1026
+ ]
1027
+ },
1028
+ "enabled": {
1029
+ "type": "object",
1030
+ "description": "When the field is enabled, as a condition over the values on screen -- what the record holds, or what the person has changed it to and not saved yet. A leaf compares one field: {field, op, value}, op one of eq, ne, in, not_in (value a list), empty, not_empty (no value). A node combines conditions: {all: [...]}, {any: [...]} or {not: {...}}, each item a condition of this same grammar. A value that is an object carrying `value` -- a lookup's {value, label}, a formatted {value, text} -- is compared by that `value`; a list (a multiple field) is eq or in when any of its items is, and ne or not_in when none is; empty is null, an empty string or an empty list. Values are compared as text, so 5 and \"5\" are the same. A field the condition names that the record does not carry is empty. A condition the component cannot read -- an unknown op, a node with no conditions -- leaves the field as it is declared.",
1031
+ "oneOf": [
1032
+ {
1033
+ "type": "object",
1034
+ "required": [
1035
+ "field",
1036
+ "op"
1037
+ ],
1038
+ "additionalProperties": false,
1039
+ "properties": {
1040
+ "field": {
1041
+ "type": "string",
1042
+ "minLength": 1,
1043
+ "description": "The key of the field compared."
1044
+ },
1045
+ "op": {
1046
+ "enum": [
1047
+ "eq",
1048
+ "ne",
1049
+ "in",
1050
+ "not_in",
1051
+ "empty",
1052
+ "not_empty"
1053
+ ]
1054
+ },
1055
+ "value": {
1056
+ "description": "What the field is compared with: a list for in and not_in, absent for empty and not_empty."
1057
+ }
1058
+ }
1059
+ },
1060
+ {
1061
+ "type": "object",
1062
+ "required": [
1063
+ "all"
1064
+ ],
1065
+ "additionalProperties": false,
1066
+ "properties": {
1067
+ "all": {
1068
+ "type": "array",
1069
+ "minItems": 1,
1070
+ "items": {
1071
+ "type": "object",
1072
+ "description": "A condition of this grammar."
1073
+ }
1074
+ }
1075
+ }
1076
+ },
1077
+ {
1078
+ "type": "object",
1079
+ "required": [
1080
+ "any"
1081
+ ],
1082
+ "additionalProperties": false,
1083
+ "properties": {
1084
+ "any": {
1085
+ "type": "array",
1086
+ "minItems": 1,
1087
+ "items": {
1088
+ "type": "object",
1089
+ "description": "A condition of this grammar."
1090
+ }
1091
+ }
1092
+ }
1093
+ },
1094
+ {
1095
+ "type": "object",
1096
+ "required": [
1097
+ "not"
1098
+ ],
1099
+ "additionalProperties": false,
1100
+ "properties": {
1101
+ "not": {
1102
+ "type": "object",
1103
+ "description": "A condition of this grammar."
1104
+ }
1105
+ }
1106
+ }
1107
+ ]
1108
+ }
1109
+ }
1110
+ }
1111
+ }
1112
+ }
1113
+ },
1114
+ "hiddenBy": {
1115
+ "type": [
1116
+ "string",
1117
+ "null"
1118
+ ],
1119
+ "enum": [
1120
+ "package",
1121
+ "shared",
1122
+ "personal",
1123
+ null
1124
+ ],
1125
+ "description": "The layer that hid this entry, when something did. The component disables un-hiding it from a layer below that one, so the rule \"hidden for everyone cannot be shown again in a personal view\" is a fact the host states per entry rather than a rule the component reimplements -- which would put the permission model in two places and let them drift."
1126
+ }
1127
+ }
1128
+ }
1129
+ },
1130
+ "hiddenBy": {
1131
+ "type": [
1132
+ "string",
1133
+ "null"
1134
+ ],
1135
+ "enum": [
1136
+ "package",
1137
+ "shared",
1138
+ "personal",
1139
+ null
1140
+ ],
1141
+ "description": "The layer that hid this entry, when something did. The component disables un-hiding it from a layer below that one, so the rule \"hidden for everyone cannot be shown again in a personal view\" is a fact the host states per entry rather than a rule the component reimplements -- which would put the permission model in two places and let them drift."
1142
+ }
1143
+ }
1144
+ }
1145
+ },
1146
+ "address": {
1147
+ "type": [
1148
+ "string",
1149
+ "null"
1150
+ ],
1151
+ "description": "Where this object lives, as the host names it. The component never constructs one: it copies this and opens this, and shows neither control when the host sends nothing. A component that invented an address would be guessing at the consumer's routing."
1152
+ },
1153
+ "view": {
1154
+ "type": [
1155
+ "object",
1156
+ "null"
1157
+ ],
1158
+ "additionalProperties": false,
1159
+ "description": "How this arrangement was resolved and what this person may change about it. Absent means the view cannot be configured at all, and the component offers nothing.",
1160
+ "properties": {
1161
+ "target": {
1162
+ "enum": [
1163
+ "package",
1164
+ "shared",
1165
+ "personal"
1166
+ ],
1167
+ "description": "The layer the arrangement in this answer resolved to."
1168
+ },
1169
+ "layers": {
1170
+ "type": "object",
1171
+ "additionalProperties": false,
1172
+ "description": "Each layer this person may write, with its revision and its stored contents. The contents are here because a save writes the layer whole: a component that knew only the resolved arrangement would have to send all of it, and a personal layer restating every entry would pin the whole view and stop any later change to the shared layer from ever showing through. So the component edits the layer it was given and writes that layer back.",
1173
+ "properties": {
1174
+ "personal": {
1175
+ "type": [
1176
+ "object",
1177
+ "null"
1178
+ ],
1179
+ "required": [
1180
+ "revision",
1181
+ "view"
1182
+ ],
1183
+ "additionalProperties": false,
1184
+ "properties": {
1185
+ "revision": {
1186
+ "type": [
1187
+ "string",
1188
+ "null"
1189
+ ],
1190
+ "description": "Null when the layer does not exist yet; the first save sends null and the host creates it."
1191
+ },
1192
+ "view": {
1193
+ "type": [
1194
+ "object",
1195
+ "null"
1196
+ ],
1197
+ "required": [
1198
+ "schema_version",
1199
+ "sections",
1200
+ "groups",
1201
+ "fields"
1202
+ ],
1203
+ "additionalProperties": false,
1204
+ "description": "What this layer stores, which is not the whole arrangement: a layer carries only the entries it decides, and the rest resolves from the layers beneath it. Null means nothing is stored in this layer yet.",
1205
+ "properties": {
1206
+ "schema_version": {
1207
+ "const": 1
1208
+ },
1209
+ "sections": {
1210
+ "type": "object"
1211
+ },
1212
+ "groups": {
1213
+ "type": "object"
1214
+ },
1215
+ "fields": {
1216
+ "type": "object"
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ },
1222
+ "shared": {
1223
+ "type": [
1224
+ "object",
1225
+ "null"
1226
+ ],
1227
+ "required": [
1228
+ "revision",
1229
+ "view"
1230
+ ],
1231
+ "additionalProperties": false,
1232
+ "properties": {
1233
+ "revision": {
1234
+ "type": [
1235
+ "string",
1236
+ "null"
1237
+ ]
1238
+ },
1239
+ "view": {
1240
+ "type": [
1241
+ "object",
1242
+ "null"
1243
+ ],
1244
+ "required": [
1245
+ "schema_version",
1246
+ "sections",
1247
+ "groups",
1248
+ "fields"
1249
+ ],
1250
+ "additionalProperties": false,
1251
+ "description": "What this layer stores, which is not the whole arrangement: a layer carries only the entries it decides, and the rest resolves from the layers beneath it. Null means nothing is stored in this layer yet.",
1252
+ "properties": {
1253
+ "schema_version": {
1254
+ "const": 1
1255
+ },
1256
+ "sections": {
1257
+ "type": "object"
1258
+ },
1259
+ "groups": {
1260
+ "type": "object"
1261
+ },
1262
+ "fields": {
1263
+ "type": "object"
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+ },
1271
+ "editable": {
1272
+ "type": "array",
1273
+ "items": {
1274
+ "enum": [
1275
+ "personal",
1276
+ "shared"
1277
+ ]
1278
+ },
1279
+ "description": "The layers this person may write. Empty means the view is read-only for them. This is per person and per collection, which is why it travels in the answer rather than in a capability: a capability is static for the host and could not express a permission one person has and another does not."
1280
+ },
1281
+ "denied": {
1282
+ "type": [
1283
+ "object",
1284
+ "null"
1285
+ ],
1286
+ "additionalProperties": false,
1287
+ "properties": {
1288
+ "personal": {
1289
+ "type": [
1290
+ "string",
1291
+ "null"
1292
+ ]
1293
+ },
1294
+ "shared": {
1295
+ "type": [
1296
+ "string",
1297
+ "null"
1298
+ ]
1299
+ }
1300
+ },
1301
+ "description": "Why a layer absent from editable cannot be written, as the host words it. Optional: without it a disabled control says something generic, which is honest but says less."
1302
+ },
1303
+ "profiles": {
1304
+ "type": "array",
1305
+ "description": "The views this person may choose between, in the order the host wants them shown. A view is a whole named arrangement, not a layer: the layers below still resolve inside the one that is chosen. One view and the component draws no chooser -- there is nothing to choose.",
1306
+ "items": {
1307
+ "type": "object",
1308
+ "required": [
1309
+ "id",
1310
+ "label"
1311
+ ],
1312
+ "additionalProperties": false,
1313
+ "properties": {
1314
+ "id": {
1315
+ "type": "string",
1316
+ "minLength": 1,
1317
+ "description": "Permanent for the life of the view, which is what makes renaming possible: a rename changes the label and nothing else, and anything pointing at the view keeps pointing at it."
1318
+ },
1319
+ "label": {
1320
+ "type": "string",
1321
+ "minLength": 1
1322
+ },
1323
+ "shared": {
1324
+ "type": "boolean",
1325
+ "description": "True when everyone in the scope sees this view, false when it is this person's own. Who may write a shared one is said by `editable`, as it already is for the shared layer."
1326
+ }
1327
+ }
1328
+ }
1329
+ },
1330
+ "profile": {
1331
+ "type": [
1332
+ "string",
1333
+ "null"
1334
+ ],
1335
+ "description": "Which of `profiles` this answer resolved to: the view the person chose last for this collection (view.profile), kept by the host; before any choice, or when that view is gone, the host's first view for them (by their group, or however it decides). The component neither stores the choice nor invents a default."
1336
+ }
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ },
1342
+ "options_page": {
1343
+ "summary": "One page of candidates for one field, with the cursor for the next page. The component holds the page for rendering and asks again rather than caching across searches.",
1344
+ "required": false,
1345
+ "schema": {
1346
+ "type": "object",
1347
+ "required": [
1348
+ "items"
1349
+ ],
1350
+ "additionalProperties": false,
1351
+ "properties": {
1352
+ "items": {
1353
+ "type": "array",
1354
+ "items": {
1355
+ "type": "object",
1356
+ "required": [
1357
+ "value",
1358
+ "label"
1359
+ ],
1360
+ "additionalProperties": false,
1361
+ "properties": {
1362
+ "value": {
1363
+ "type": [
1364
+ "string",
1365
+ "integer"
1366
+ ]
1367
+ },
1368
+ "label": {
1369
+ "type": "string",
1370
+ "description": "What a person reads. The label travels with the value so the chosen record can be shown without a second read."
1371
+ }
1372
+ }
1373
+ }
1374
+ },
1375
+ "next_cursor": {
1376
+ "type": [
1377
+ "string",
1378
+ "integer",
1379
+ "null"
1380
+ ],
1381
+ "description": "Opaque to the component: it is sent back unchanged to ask for the next page. Absent or null means this is the last page."
1382
+ },
1383
+ "total": {
1384
+ "type": [
1385
+ "integer",
1386
+ "null"
1387
+ ]
1388
+ }
1389
+ }
1390
+ }
1391
+ },
1392
+ "confirm": {
1393
+ "summary": "A save the host will make only once confirmed, since 1.14.0 -- a change that reaches past this object, such as one that moves every record of a type. It is carried by a refused answer with reason `confirm_required`, beside `answer`; the component shows the summary, and when the person confirms it raises the same save again with `confirm` set to the digest. The host checks the digest against the save it described, and a digest that no longer matches is answered with a new confirmation rather than applied. Cancelling sends nothing and keeps what the person entered. The same shape as the data view port's.",
1394
+ "required": false,
1395
+ "schema": {
1396
+ "type": "object",
1397
+ "required": [
1398
+ "summary",
1399
+ "digest"
1400
+ ],
1401
+ "additionalProperties": false,
1402
+ "properties": {
1403
+ "summary": {
1404
+ "type": "string",
1405
+ "minLength": 1,
1406
+ "description": "What the save will do, in words the person reads before confirming."
1407
+ },
1408
+ "digest": {
1409
+ "type": "string",
1410
+ "minLength": 1,
1411
+ "description": "Opaque to the component: sent back unchanged as `confirm`."
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ },
1417
+ "intents": {
1418
+ "record.load": {
1419
+ "summary": "Read one object with its arrangement; with `mode: create` and no `record_id`, an empty object of the collection, defaults filled and laid out by the collection's arrangement (1.12.0).",
1420
+ "event": "sf-property-view-state",
1421
+ "payload": {
1422
+ "type": "object",
1423
+ "required": [
1424
+ "record_id",
1425
+ "collection",
1426
+ "reason"
1427
+ ],
1428
+ "additionalProperties": false,
1429
+ "properties": {
1430
+ "record_id": {
1431
+ "type": [
1432
+ "string",
1433
+ "integer",
1434
+ "null"
1435
+ ]
1436
+ },
1437
+ "collection": {
1438
+ "type": [
1439
+ "string",
1440
+ "null"
1441
+ ]
1442
+ },
1443
+ "reason": {
1444
+ "type": "string",
1445
+ "description": "Why the component is asking: connected, host-connected, record-id-changed or requested. For the host's own logging; it must not change the answer."
1446
+ },
1447
+ "mode": {
1448
+ "enum": [
1449
+ "read",
1450
+ "create"
1451
+ ],
1452
+ "description": "What the object is read for. Absent is `read`. `create` asks for an empty object of `collection`: no `record_id`, no revision, every field the person may fill, laid out as the collection's records are."
1453
+ }
1454
+ }
1455
+ },
1456
+ "carries_revision": false,
1457
+ "answers": [
1458
+ "applied",
1459
+ "refused",
1460
+ "unavailable"
1461
+ ],
1462
+ "answer_data": {
1463
+ "type": "object",
1464
+ "required": [
1465
+ "record"
1466
+ ],
1467
+ "properties": {
1468
+ "record": {
1469
+ "type": "object"
1470
+ }
1471
+ }
1472
+ }
1473
+ },
1474
+ "group.save": {
1475
+ "summary": "Save the fields a person changed in one group, as one revision. Sent only when the save button is pressed -- never on blur and never per field. Since 1.14.0 the host may ask for a confirmation first: refused with reason confirm_required and `confirm`, then raised again with `confirm` set to its digest. A field hidden by its condition (`when.visible`) is never in values.",
1476
+ "event": "sf-property-view-state",
1477
+ "payload": {
1478
+ "type": "object",
1479
+ "required": [
1480
+ "record_id",
1481
+ "collection",
1482
+ "section",
1483
+ "group",
1484
+ "values",
1485
+ "revision"
1486
+ ],
1487
+ "additionalProperties": false,
1488
+ "properties": {
1489
+ "record_id": {
1490
+ "type": [
1491
+ "string",
1492
+ "integer",
1493
+ "null"
1494
+ ]
1495
+ },
1496
+ "collection": {
1497
+ "type": [
1498
+ "string",
1499
+ "null"
1500
+ ]
1501
+ },
1502
+ "section": {
1503
+ "type": "string",
1504
+ "minLength": 1
1505
+ },
1506
+ "group": {
1507
+ "type": "string",
1508
+ "minLength": 1
1509
+ },
1510
+ "values": {
1511
+ "type": "object",
1512
+ "description": "Only the fields whose value the person changed, keyed by field key. A field the person did not touch is absent, so a save cannot overwrite what another save changed meanwhile.",
1513
+ "additionalProperties": true
1514
+ },
1515
+ "revision": {
1516
+ "type": [
1517
+ "string",
1518
+ "integer",
1519
+ "null"
1520
+ ],
1521
+ "description": "The revision the read carried. The host compares and sets; a stale one is a conflict, never a silent overwrite."
1522
+ },
1523
+ "confirm": {
1524
+ "type": "string",
1525
+ "minLength": 1,
1526
+ "description": "The digest of a confirmation the host asked for (refused, reason confirm_required). Absent on the first raise."
1527
+ }
1528
+ }
1529
+ },
1530
+ "carries_revision": true,
1531
+ "answers": [
1532
+ "applied",
1533
+ "conflict",
1534
+ "refused",
1535
+ "unavailable"
1536
+ ],
1537
+ "answer_data": {
1538
+ "type": "object",
1539
+ "properties": {
1540
+ "record": {
1541
+ "type": "object"
1542
+ },
1543
+ "errors": {
1544
+ "type": "object"
1545
+ },
1546
+ "confirm": {
1547
+ "type": "object",
1548
+ "description": "With reason confirm_required: what to show and the digest to send back."
1549
+ }
1550
+ }
1551
+ }
1552
+ },
1553
+ "field.options": {
1554
+ "summary": "Read one page of candidate records for a reference field, optionally narrowed by a search. Never a write. Since 1.14.0 it carries the field's `source`, the reference list a lookup chooses from, and a `limit`, the most candidates the picker draws at once. A lookup with its options inline is never asked for.",
1555
+ "event": "sf-property-view-state",
1556
+ "payload": {
1557
+ "type": "object",
1558
+ "required": [
1559
+ "record_id",
1560
+ "collection",
1561
+ "field",
1562
+ "cursor",
1563
+ "search"
1564
+ ],
1565
+ "additionalProperties": false,
1566
+ "properties": {
1567
+ "record_id": {
1568
+ "type": [
1569
+ "string",
1570
+ "integer",
1571
+ "null"
1572
+ ]
1573
+ },
1574
+ "collection": {
1575
+ "type": [
1576
+ "string",
1577
+ "null"
1578
+ ]
1579
+ },
1580
+ "field": {
1581
+ "type": "string",
1582
+ "minLength": 1
1583
+ },
1584
+ "cursor": {
1585
+ "type": [
1586
+ "string",
1587
+ "integer",
1588
+ "null"
1589
+ ],
1590
+ "description": "The next_cursor of the previous answer, or null for the first page. The component never constructs one."
1591
+ },
1592
+ "search": {
1593
+ "type": "string",
1594
+ "description": "What the person typed. The search belongs to the host: the component does not filter the page it was given, because the page is not the whole set."
1595
+ },
1596
+ "source": {
1597
+ "type": [
1598
+ "string",
1599
+ "null"
1600
+ ],
1601
+ "description": "The field's `source`, as it arrived. Absent for a field that names none."
1602
+ },
1603
+ "limit": {
1604
+ "type": "integer",
1605
+ "minimum": 1,
1606
+ "description": "The most candidates the page may hold. The host may answer fewer, and a page that is not the last says so by its next_cursor rather than by being full."
1607
+ }
1608
+ }
1609
+ },
1610
+ "carries_revision": false,
1611
+ "answers": [
1612
+ "applied",
1613
+ "refused",
1614
+ "unavailable"
1615
+ ],
1616
+ "capability": "field-options",
1617
+ "answer_data": {
1618
+ "type": "object",
1619
+ "required": [
1620
+ "options_page"
1621
+ ],
1622
+ "properties": {
1623
+ "options_page": {
1624
+ "type": "object"
1625
+ }
1626
+ }
1627
+ }
1628
+ },
1629
+ "field.upload": {
1630
+ "summary": "Take one file the person chose and store it. Answered with the stored file in the same shape the field reads, so the view needs no second read.",
1631
+ "event": "sf-property-view-state",
1632
+ "payload": {
1633
+ "type": "object",
1634
+ "required": [
1635
+ "record_id",
1636
+ "collection",
1637
+ "field",
1638
+ "file"
1639
+ ],
1640
+ "additionalProperties": false,
1641
+ "properties": {
1642
+ "record_id": {
1643
+ "type": [
1644
+ "string",
1645
+ "integer",
1646
+ "null"
1647
+ ]
1648
+ },
1649
+ "collection": {
1650
+ "type": [
1651
+ "string",
1652
+ "null"
1653
+ ]
1654
+ },
1655
+ "field": {
1656
+ "type": "string",
1657
+ "minLength": 1
1658
+ },
1659
+ "file": {
1660
+ "description": "The File the person chose, passed as it is. Not a data URL and not base64: the host decides how it travels, and a component that encoded it would double the bytes of every upload to no purpose."
1661
+ }
1662
+ }
1663
+ },
1664
+ "carries_revision": false,
1665
+ "answers": [
1666
+ "applied",
1667
+ "refused",
1668
+ "unavailable"
1669
+ ],
1670
+ "capability": "field-upload",
1671
+ "answer_data": {
1672
+ "type": "object",
1673
+ "required": [
1674
+ "file"
1675
+ ],
1676
+ "properties": {
1677
+ "file": {
1678
+ "type": "object"
1679
+ }
1680
+ }
1681
+ }
1682
+ },
1683
+ "record.delete": {
1684
+ "summary": "The person asked for the record to be deleted. Whether to ask first, and whether to delete at all, is the host's: the component raises the intent and closes when the host says it is done.",
1685
+ "event": "sf-property-view-delete-record",
1686
+ "payload": {
1687
+ "type": "object",
1688
+ "required": [
1689
+ "record_id",
1690
+ "collection"
1691
+ ],
1692
+ "additionalProperties": false,
1693
+ "properties": {
1694
+ "record_id": {
1695
+ "type": [
1696
+ "string",
1697
+ "integer",
1698
+ "null"
1699
+ ]
1700
+ },
1701
+ "collection": {
1702
+ "type": [
1703
+ "string",
1704
+ "null"
1705
+ ]
1706
+ },
1707
+ "revision": {
1708
+ "type": [
1709
+ "string",
1710
+ "integer",
1711
+ "null"
1712
+ ],
1713
+ "description": "The revision the panel was reading, so a host can refuse a delete aimed at a record that has moved on."
1714
+ }
1715
+ }
1716
+ },
1717
+ "carries_revision": true,
1718
+ "answers": [
1719
+ "applied",
1720
+ "conflict",
1721
+ "refused",
1722
+ "unavailable"
1723
+ ],
1724
+ "answer": {
1725
+ "applied": "The record is gone and the panel closes.",
1726
+ "conflict": "The record moved on since it was read, so nothing was deleted.",
1727
+ "refused": "Nothing was deleted; `message` or `reason` is shown and the panel stays open.",
1728
+ "unavailable": "The host does not delete records here."
1729
+ }
1730
+ },
1731
+ "record.open": {
1732
+ "summary": "The person activated a reference value. What to open, and where, is the host's: it may push another panel onto the stack, route to a full page, or refuse.",
1733
+ "event": "sf-property-view-open-record",
1734
+ "payload": {
1735
+ "type": "object",
1736
+ "required": [
1737
+ "record_id",
1738
+ "collection",
1739
+ "field",
1740
+ "value"
1741
+ ],
1742
+ "additionalProperties": false,
1743
+ "properties": {
1744
+ "record_id": {
1745
+ "type": [
1746
+ "string",
1747
+ "integer",
1748
+ "null"
1749
+ ],
1750
+ "description": "The object the person was looking at, not the one being opened."
1751
+ },
1752
+ "collection": {
1753
+ "type": [
1754
+ "string",
1755
+ "null"
1756
+ ]
1757
+ },
1758
+ "field": {
1759
+ "type": "string",
1760
+ "minLength": 1
1761
+ },
1762
+ "value": {
1763
+ "type": "object",
1764
+ "required": [
1765
+ "value"
1766
+ ],
1767
+ "additionalProperties": true,
1768
+ "properties": {
1769
+ "value": {
1770
+ "type": [
1771
+ "string",
1772
+ "integer"
1773
+ ]
1774
+ },
1775
+ "label": {
1776
+ "type": "string"
1777
+ },
1778
+ "href": {
1779
+ "type": [
1780
+ "string",
1781
+ "null"
1782
+ ]
1783
+ }
1784
+ },
1785
+ "description": "The reference as the field holds it. The component passes it on and decides nothing: a composite that followed the href itself would be routing for the consumer, and a composite that opened a window itself would be deciding whether a half-edited window may be covered."
1786
+ }
1787
+ }
1788
+ },
1789
+ "carries_revision": false,
1790
+ "answers": [
1791
+ "applied",
1792
+ "refused",
1793
+ "unavailable"
1794
+ ],
1795
+ "capability": "record-open"
1796
+ },
1797
+ "view.save": {
1798
+ "summary": "Save the arrangement of this collection in one layer, as one revision. Sent when a person changes it -- a drag, a width, a variant, a hint, a field taken out of an area, an area or a section made, renamed or removed -- and applied immediately, the way column settings are, rather than waiting for a button.",
1799
+ "event": "sf-property-view-state",
1800
+ "capability": "view-settings",
1801
+ "carries_revision": true,
1802
+ "payload": {
1803
+ "type": "object",
1804
+ "required": [
1805
+ "collection",
1806
+ "target",
1807
+ "view"
1808
+ ],
1809
+ "additionalProperties": false,
1810
+ "properties": {
1811
+ "record_id": {
1812
+ "type": [
1813
+ "string",
1814
+ "null"
1815
+ ],
1816
+ "description": "Null, because an arrangement belongs to the collection rather than to one record. Declared so a host that keeps it per record has somewhere truthful to read it from, not so the component may send one."
1817
+ },
1818
+ "collection": {
1819
+ "type": "string",
1820
+ "minLength": 1
1821
+ },
1822
+ "target": {
1823
+ "type": "object",
1824
+ "required": [
1825
+ "kind",
1826
+ "revision"
1827
+ ],
1828
+ "additionalProperties": false,
1829
+ "properties": {
1830
+ "kind": {
1831
+ "enum": [
1832
+ "personal",
1833
+ "shared"
1834
+ ]
1835
+ },
1836
+ "revision": {
1837
+ "type": [
1838
+ "string",
1839
+ "null"
1840
+ ],
1841
+ "description": "The revision this write is against. Null writes a layer that does not exist yet."
1842
+ }
1843
+ }
1844
+ },
1845
+ "view": {
1846
+ "type": "object",
1847
+ "required": [
1848
+ "schema_version",
1849
+ "sections",
1850
+ "groups",
1851
+ "fields"
1852
+ ],
1853
+ "additionalProperties": false,
1854
+ "description": "The whole layer, never a patch of it. A per-entry write would let two people reordering the same view interleave into an arrangement neither chose; a whole-layer compare-and-set makes them collide instead, which is the same reason a group saves as one revision.",
1855
+ "properties": {
1856
+ "schema_version": {
1857
+ "const": 1
1858
+ },
1859
+ "sections": {
1860
+ "type": "object",
1861
+ "description": "Keyed by section key: label, icon, order, hidden, rare. A key the record does not carry is a section this view asks the host to keep: the component draws it at once from the layer, and it survives the next read only if the host returns it in record.load. A host that will not keep one answers refused with its reason rather than applied. `icon` is this view's glyph for the section, by the name the Framework's icon set knows it, and null takes it back to the host's."
1862
+ },
1863
+ "groups": {
1864
+ "type": "object",
1865
+ "description": "Keyed by group key: label, order, section, columns, hidden, rare. A key the record does not carry is an area this view asks the host to keep, on the same terms as a section. `label` is this view's name for the area, so resetting the layer gives the host's name back."
1866
+ },
1867
+ "fields": {
1868
+ "type": "object",
1869
+ "description": "Keyed by field key: group, order, hidden, width, variant, hint, label. A null group takes the field out of the arrangement: it joins `unplaced` and is drawn only while the view is being arranged. `label` is this view's name for the field, the same way a section and an area carry one, so resetting the layer gives the host's name back."
1870
+ }
1871
+ }
1872
+ }
1873
+ }
1874
+ },
1875
+ "answers": [
1876
+ "applied",
1877
+ "conflict",
1878
+ "refused",
1879
+ "unavailable"
1880
+ ],
1881
+ "answer": {
1882
+ "applied": {
1883
+ "schema": {
1884
+ "type": "object",
1885
+ "required": [
1886
+ "revision"
1887
+ ],
1888
+ "properties": {
1889
+ "revision": {
1890
+ "type": "string",
1891
+ "description": "The revision the layer now has, for the next write."
1892
+ },
1893
+ "view": {
1894
+ "type": [
1895
+ "object",
1896
+ "null"
1897
+ ],
1898
+ "description": "The stored layer as the host normalised it, when that differs from what was sent. The component redraws from this rather than from its own optimistic copy."
1899
+ }
1900
+ }
1901
+ }
1902
+ },
1903
+ "conflict": {
1904
+ "schema": {
1905
+ "type": "object",
1906
+ "required": [
1907
+ "revision",
1908
+ "view"
1909
+ ],
1910
+ "properties": {
1911
+ "revision": {
1912
+ "type": "string"
1913
+ },
1914
+ "view": {
1915
+ "type": "object",
1916
+ "description": "The layer as it is now. A conflict that carried only a revision would leave the component drawing an arrangement nobody stored."
1917
+ }
1918
+ }
1919
+ }
1920
+ },
1921
+ "refused": {
1922
+ "schema": {
1923
+ "type": "object",
1924
+ "properties": {
1925
+ "reason": {
1926
+ "type": [
1927
+ "string",
1928
+ "null"
1929
+ ]
1930
+ },
1931
+ "entry_errors": {
1932
+ "type": [
1933
+ "object",
1934
+ "null"
1935
+ ],
1936
+ "description": "Why entries were rejected, keyed by kind and key -- \"section:<key>\", \"group:<key>\", \"field:<key>\". Keyed by kind because a section and a field may share a key, so a bare key could name two different entries and the error would be shown under the wrong one. This differs from group.save, whose field_errors are keyed bare: there only fields can fail, so there is nothing to collide with."
1937
+ }
1938
+ }
1939
+ }
1940
+ }
1941
+ },
1942
+ "answer_data": {
1943
+ "type": "object",
1944
+ "properties": {
1945
+ "revision": {
1946
+ "type": [
1947
+ "string",
1948
+ "integer"
1949
+ ]
1950
+ },
1951
+ "view": {
1952
+ "type": "object"
1953
+ },
1954
+ "entry_errors": {
1955
+ "type": "object"
1956
+ }
1957
+ }
1958
+ }
1959
+ },
1960
+ "view.reset": {
1961
+ "summary": "Return one layer to what lies beneath it. A write against the layer revision, never a deletion -- and never a write of the component idea of the defaults, because the host owns those and writing them would overwrite them with a guess.",
1962
+ "event": "sf-property-view-state",
1963
+ "capability": "view-settings",
1964
+ "carries_revision": true,
1965
+ "payload": {
1966
+ "type": "object",
1967
+ "required": [
1968
+ "collection",
1969
+ "target"
1970
+ ],
1971
+ "additionalProperties": false,
1972
+ "properties": {
1973
+ "record_id": {
1974
+ "type": [
1975
+ "string",
1976
+ "null"
1977
+ ]
1978
+ },
1979
+ "collection": {
1980
+ "type": "string",
1981
+ "minLength": 1
1982
+ },
1983
+ "target": {
1984
+ "type": "object",
1985
+ "required": [
1986
+ "kind",
1987
+ "revision"
1988
+ ],
1989
+ "additionalProperties": false,
1990
+ "properties": {
1991
+ "kind": {
1992
+ "enum": [
1993
+ "personal",
1994
+ "shared"
1995
+ ]
1996
+ },
1997
+ "revision": {
1998
+ "type": [
1999
+ "string",
2000
+ "null"
2001
+ ],
2002
+ "description": "The revision this write is against. Null writes a layer that does not exist yet."
2003
+ }
2004
+ }
2005
+ }
2006
+ }
2007
+ },
2008
+ "answers": [
2009
+ "applied",
2010
+ "conflict",
2011
+ "refused",
2012
+ "unavailable"
2013
+ ],
2014
+ "answer": {
2015
+ "applied": {
2016
+ "schema": {
2017
+ "type": "object",
2018
+ "required": [
2019
+ "revision",
2020
+ "view"
2021
+ ],
2022
+ "properties": {
2023
+ "revision": {
2024
+ "type": "string"
2025
+ },
2026
+ "view": {
2027
+ "type": "object",
2028
+ "description": "The arrangement that now resolves, after the reset -- the layers beneath the one cleared. The panel redraws from it instead of reading the record again, which would show the person a second load for a change they already made."
2029
+ }
2030
+ }
2031
+ }
2032
+ },
2033
+ "conflict": {
2034
+ "schema": {
2035
+ "type": "object",
2036
+ "required": [
2037
+ "revision",
2038
+ "view"
2039
+ ],
2040
+ "properties": {
2041
+ "revision": {
2042
+ "type": "string"
2043
+ },
2044
+ "view": {
2045
+ "type": "object"
2046
+ }
2047
+ }
2048
+ }
2049
+ }
2050
+ },
2051
+ "answer_data": {
2052
+ "type": "object",
2053
+ "properties": {
2054
+ "revision": {
2055
+ "type": [
2056
+ "string",
2057
+ "integer"
2058
+ ]
2059
+ }
2060
+ }
2061
+ }
2062
+ },
2063
+ "view.profile": {
2064
+ "summary": "Asks for a different view by its id. The choice is the person's own (1.13.0): the host keeps it per person and collection, and record.load and record.open answer through it until the person chooses another or that view is removed. The host answers as it answers record.load -- the record and its view -- because a view is a whole arrangement and reading it is reading the record through it.",
2065
+ "event": "sf-property-view-state",
2066
+ "capability": "view-settings",
2067
+ "carries_revision": false,
2068
+ "payload": {
2069
+ "type": "object",
2070
+ "required": [
2071
+ "collection",
2072
+ "profile"
2073
+ ],
2074
+ "additionalProperties": true,
2075
+ "properties": {
2076
+ "record_id": {
2077
+ "type": [
2078
+ "string",
2079
+ "null"
2080
+ ]
2081
+ },
2082
+ "collection": {
2083
+ "type": "string"
2084
+ },
2085
+ "profile": {
2086
+ "type": "string",
2087
+ "minLength": 1,
2088
+ "description": "The id of the view asked for."
2089
+ }
2090
+ }
2091
+ },
2092
+ "answers": [
2093
+ "applied",
2094
+ "refused",
2095
+ "unavailable"
2096
+ ],
2097
+ "answer_data": {
2098
+ "type": "object",
2099
+ "properties": {
2100
+ "record": {
2101
+ "type": "object"
2102
+ }
2103
+ }
2104
+ }
2105
+ },
2106
+ "view.create": {
2107
+ "summary": "Makes a new named view. The host gives it an id and answers as it answers a read, with the record through the new view: a person who has just made a view is looking at it. What the new view holds is what was on screen when it was made -- the component sends the arrangement rather than leaving the host to guess at a starting point.",
2108
+ "event": "sf-property-view-state",
2109
+ "capability": "view-settings",
2110
+ "carries_revision": false,
2111
+ "payload": {
2112
+ "type": "object",
2113
+ "required": [
2114
+ "collection",
2115
+ "label",
2116
+ "shared",
2117
+ "view"
2118
+ ],
2119
+ "additionalProperties": true,
2120
+ "properties": {
2121
+ "record_id": {
2122
+ "type": [
2123
+ "string",
2124
+ "null"
2125
+ ]
2126
+ },
2127
+ "collection": {
2128
+ "type": "string"
2129
+ },
2130
+ "label": {
2131
+ "type": "string",
2132
+ "minLength": 1
2133
+ },
2134
+ "shared": {
2135
+ "type": "boolean",
2136
+ "description": "Whether everyone in the scope gets it. A host may refuse a shared one."
2137
+ },
2138
+ "view": {
2139
+ "type": "object",
2140
+ "description": "The arrangement to start from, which is the one on screen."
2141
+ }
2142
+ }
2143
+ },
2144
+ "answers": [
2145
+ "applied",
2146
+ "refused",
2147
+ "unavailable"
2148
+ ]
2149
+ },
2150
+ "view.rename": {
2151
+ "summary": "Changes a view's label and nothing else. The id is permanent, which is what makes this a rename rather than a replacement: anything pointing at the view keeps pointing at it.",
2152
+ "event": "sf-property-view-state",
2153
+ "capability": "view-settings",
2154
+ "carries_revision": false,
2155
+ "payload": {
2156
+ "type": "object",
2157
+ "required": [
2158
+ "collection",
2159
+ "profile",
2160
+ "label"
2161
+ ],
2162
+ "additionalProperties": true,
2163
+ "properties": {
2164
+ "record_id": {
2165
+ "type": [
2166
+ "string",
2167
+ "null"
2168
+ ]
2169
+ },
2170
+ "collection": {
2171
+ "type": "string"
2172
+ },
2173
+ "profile": {
2174
+ "type": "string",
2175
+ "minLength": 1
2176
+ },
2177
+ "label": {
2178
+ "type": "string",
2179
+ "minLength": 1
2180
+ }
2181
+ }
2182
+ },
2183
+ "answers": [
2184
+ "applied",
2185
+ "refused",
2186
+ "unavailable"
2187
+ ]
2188
+ },
2189
+ "view.delete": {
2190
+ "summary": "Removes a named view. The host refuses when it is the last shared one, because a scope with no shared view has nothing for a person who has made none of their own -- and the component does not offer the control in that case either, so the refusal is a guard rather than the way a person finds out.",
2191
+ "event": "sf-property-view-state",
2192
+ "capability": "view-settings",
2193
+ "carries_revision": false,
2194
+ "payload": {
2195
+ "type": "object",
2196
+ "required": [
2197
+ "collection",
2198
+ "profile"
2199
+ ],
2200
+ "additionalProperties": true,
2201
+ "properties": {
2202
+ "record_id": {
2203
+ "type": [
2204
+ "string",
2205
+ "null"
2206
+ ]
2207
+ },
2208
+ "collection": {
2209
+ "type": "string"
2210
+ },
2211
+ "profile": {
2212
+ "type": "string",
2213
+ "minLength": 1
2214
+ }
2215
+ }
2216
+ },
2217
+ "answers": [
2218
+ "applied",
2219
+ "refused",
2220
+ "unavailable"
2221
+ ]
2222
+ },
2223
+ "record.create": {
2224
+ "summary": "Make the object a create surface was filled for, as one write, when the create button is pressed. The values are every field the person filled; a field left empty is absent.",
2225
+ "event": "sf-property-view-state",
2226
+ "payload": {
2227
+ "type": "object",
2228
+ "required": [
2229
+ "collection",
2230
+ "values"
2231
+ ],
2232
+ "additionalProperties": false,
2233
+ "properties": {
2234
+ "collection": {
2235
+ "type": [
2236
+ "string",
2237
+ "null"
2238
+ ]
2239
+ },
2240
+ "values": {
2241
+ "type": "object",
2242
+ "additionalProperties": true,
2243
+ "description": "The fields the person filled, keyed by field key."
2244
+ }
2245
+ }
2246
+ },
2247
+ "carries_revision": false,
2248
+ "answers": [
2249
+ "applied",
2250
+ "refused",
2251
+ "unavailable"
2252
+ ],
2253
+ "answer_data": {
2254
+ "type": "object",
2255
+ "properties": {
2256
+ "record_id": {
2257
+ "type": [
2258
+ "string",
2259
+ "integer"
2260
+ ]
2261
+ },
2262
+ "record": {
2263
+ "type": "object"
2264
+ },
2265
+ "errors": {
2266
+ "type": "object"
2267
+ }
2268
+ }
2269
+ }
2270
+ },
2271
+ "record.save": {
2272
+ "summary": "Save the whole object as one revision, from a surface where every group is open at once (mode edit). Only the fields the person changed are sent, as in a group save. Since 1.14.0 the host may ask for a confirmation first: refused with reason confirm_required and `confirm`, then raised again with `confirm` set to its digest. A field hidden by its condition (`when.visible`) is never in values.",
2273
+ "event": "sf-property-view-state",
2274
+ "payload": {
2275
+ "type": "object",
2276
+ "required": [
2277
+ "record_id",
2278
+ "collection",
2279
+ "values",
2280
+ "revision"
2281
+ ],
2282
+ "additionalProperties": false,
2283
+ "properties": {
2284
+ "record_id": {
2285
+ "type": [
2286
+ "string",
2287
+ "integer"
2288
+ ]
2289
+ },
2290
+ "collection": {
2291
+ "type": [
2292
+ "string",
2293
+ "null"
2294
+ ]
2295
+ },
2296
+ "values": {
2297
+ "type": "object",
2298
+ "additionalProperties": true,
2299
+ "description": "Only the fields the person changed, in any group."
2300
+ },
2301
+ "revision": {
2302
+ "type": [
2303
+ "string",
2304
+ "integer",
2305
+ "null"
2306
+ ],
2307
+ "description": "The revision the read carried; a stale one is a conflict."
2308
+ },
2309
+ "confirm": {
2310
+ "type": "string",
2311
+ "minLength": 1,
2312
+ "description": "The digest of a confirmation the host asked for (refused, reason confirm_required). Absent on the first raise."
2313
+ }
2314
+ }
2315
+ },
2316
+ "carries_revision": true,
2317
+ "answers": [
2318
+ "applied",
2319
+ "conflict",
2320
+ "refused",
2321
+ "unavailable"
2322
+ ],
2323
+ "answer_data": {
2324
+ "type": "object",
2325
+ "properties": {
2326
+ "record": {
2327
+ "type": "object"
2328
+ },
2329
+ "revision": {
2330
+ "type": [
2331
+ "string",
2332
+ "integer"
2333
+ ]
2334
+ },
2335
+ "errors": {
2336
+ "type": "object"
2337
+ },
2338
+ "confirm": {
2339
+ "type": "object",
2340
+ "description": "With reason confirm_required: what to show and the digest to send back."
2341
+ }
2342
+ }
2343
+ }
2344
+ }
2345
+ },
2346
+ "settings": {
2347
+ "layers": [
2348
+ "package",
2349
+ "shared",
2350
+ "personal"
2351
+ ],
2352
+ "shared_only_fields": [],
2353
+ "reset": {
2354
+ "semantics": "compare-and-set",
2355
+ "summary": "Resetting a layer is a write against its revision, never a deletion. view.reset carries it."
2356
+ },
2357
+ "target": {
2358
+ "kinds": [
2359
+ "personal",
2360
+ "shared"
2361
+ ],
2362
+ "carries_revision": true
2363
+ },
2364
+ "fields": {
2365
+ "summary": "Per-field view settings a layer carries: order, group, section, template variant, width, hint and visibility.",
2366
+ "writable_in": "1.6.0"
2367
+ }
2368
+ },
2369
+ "conformance": {
2370
+ "kit": "kit",
2371
+ "sha256": "365bfc0aca7e2ef01b18a0084c67603fa55d54f48184711648d2b9c7d2622e55",
2372
+ "cases": 51
2373
+ },
2374
+ "answer": {
2375
+ "summary": "Every intent is answered with one object: `answer` says what happened, `reason` why it was refused or is unavailable, `message` what the panel may show, and the intent's own fields sit beside them -- `record`, `revision`, `view`, `file`, `errors` -- not under a `data` key. Each intent's answer_data names its fields. A save the host will make only once confirmed is refused with reason `confirm_required` and carries `confirm` (1.14.0).",
2376
+ "schema": {
2377
+ "type": "object",
2378
+ "required": [
2379
+ "answer"
2380
+ ],
2381
+ "properties": {
2382
+ "answer": {
2383
+ "enum": [
2384
+ "applied",
2385
+ "conflict",
2386
+ "refused",
2387
+ "unavailable"
2388
+ ]
2389
+ },
2390
+ "reason": {
2391
+ "type": "string",
2392
+ "description": "Why the intent was refused or is unavailable. Free text a host may extend; since 1.14.0 `confirm_required` asks for a confirmation and comes with `confirm`."
2393
+ },
2394
+ "message": {
2395
+ "type": "string"
2396
+ },
2397
+ "record": {
2398
+ "type": "object",
2399
+ "description": "The record as the panel shows it: values, sections and view."
2400
+ },
2401
+ "revision": {
2402
+ "type": [
2403
+ "string",
2404
+ "integer"
2405
+ ]
2406
+ },
2407
+ "view": {
2408
+ "type": "object"
2409
+ },
2410
+ "file": {
2411
+ "type": "object"
2412
+ },
2413
+ "errors": {
2414
+ "type": "object",
2415
+ "description": "Per-field errors of a refused group save."
2416
+ },
2417
+ "entry_errors": {
2418
+ "type": "object"
2419
+ },
2420
+ "options_page": {
2421
+ "type": "object"
2422
+ },
2423
+ "record_id": {
2424
+ "type": [
2425
+ "string",
2426
+ "integer"
2427
+ ],
2428
+ "description": "The id of an object a create made."
2429
+ },
2430
+ "confirm": {
2431
+ "type": "object",
2432
+ "description": "What a save the host confirms first will do, and the digest to send back (1.14.0)."
2433
+ }
2434
+ }
2435
+ }
2436
+ },
2437
+ "compatibility": {
2438
+ "previous_major": null,
2439
+ "supported_until": null,
2440
+ "recheck": [
2441
+ "the answer carries the object and its arrangement, not a flat list of values",
2442
+ "a refused read is explained rather than rendered as empty",
2443
+ "a late answer is dropped rather than drawn over the current object",
2444
+ "rare fields arrive declared, and hiding them changes no value",
2445
+ "the host supplies the property type and never the component to draw it with",
2446
+ "a save carries only the fields the person changed",
2447
+ "a stale revision is answered with a conflict rather than an overwrite",
2448
+ "field errors come back keyed by field and are shown under their own field",
2449
+ "nothing is saved on blur or per field",
2450
+ "candidate records are a page with a cursor, never the whole set",
2451
+ "the search reaches the host rather than filtering the page already loaded",
2452
+ "a chosen record the page does not hold is still shown and still written",
2453
+ "a host without the field-options capability shows the value read-only rather than an empty control",
2454
+ "the address of a window is the host's, and a component without one offers neither copying nor opening",
2455
+ "a file is taken by the host, which reports its own progress; the component times nothing",
2456
+ "removing a stored file is a value change saved by the button, never an upload intent",
2457
+ "the stored file comes back in the shape the field reads, so no second read is needed",
2458
+ "activating a reference asks the host what to open rather than following a link",
2459
+ "a window left half-edited underneath another keeps its unsaved changes, because each window is its own element",
2460
+ "every field key a renderer reads is declared, so a host that validates its own answers against this schema is not refused for sending one",
2461
+ "a layer is written whole against its revision, so two people rearranging the same view collide rather than interleave",
2462
+ "the layer a person may write is stated per person in the answer, not assumed from a capability",
2463
+ "a hidden entry names the layer that hid it, so the component disables un-hiding instead of reimplementing the permission rule",
2464
+ "a reset answers with the arrangement that now resolves, so the panel redraws without a second read",
2465
+ "the layer a person edits arrives with its own contents, so a save writes that layer rather than restating the whole arrangement into it",
2466
+ "a related group composes a data view that asks for its own host port, rather than records travelling through the property-view seam",
2467
+ "a field that falls back to something says what it falls back to, so «not set» and «set to the same value» are not drawn alike",
2468
+ "an area or a section the layer names, and the record did not carry, is kept by the host or refused with a reason",
2469
+ "a field taken out of an area joins the unplaced rather than being hidden, and is offered back there",
2470
+ "a control that leads out of the panel is drawn only where the host says it leads somewhere",
2471
+ "the answer is one object: answer, reason, message and the intent's own fields beside them; each intent names its fields",
2472
+ "a person without record.edit is offered no way to change a field, and a host that means read-only says so by leaving record.edit out",
2473
+ "an empty object for a create is laid out by the collection's arrangement, so a view changed once is changed for creating too",
2474
+ "a create answers with the new object's id, or refuses with errors keyed by field",
2475
+ "a whole-object save is one revision, carries only the changed fields and conflicts on a stale revision",
2476
+ "a lookup's value is {value, label}, or a list of them when multiple, and a save writes the same shape back",
2477
+ "a lookup's candidates come from the host, named by its source and searched by the host; a short list arrives inline and is never asked for",
2478
+ "field.options carries the source and a limit, and a page says it is the last by its next_cursor, not by its length",
2479
+ "a save the host confirms first is refused with confirm_required and a digest, and applied only when raised again with that digest",
2480
+ "a confirmation that no longer matches is asked again rather than applied, and cancelling one sends nothing and keeps what was entered",
2481
+ "a field hidden by its condition is not sent in a save and is not cleared by one"
2482
+ ]
2483
+ }
2484
+ }