@object-ui/core 17.6.0 → 18.0.0

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 (147) hide show
  1. package/CHANGELOG.md +7260 -0
  2. package/README.md +228 -14
  3. package/dist/actions/ActionEngine.d.ts +4 -2
  4. package/dist/actions/ActionEngine.js +13 -8
  5. package/dist/actions/ActionRunner.d.ts +473 -69
  6. package/dist/actions/ActionRunner.js +792 -125
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/UndoManager.d.ts +8 -13
  9. package/dist/actions/actionKeys.d.ts +9 -7
  10. package/dist/actions/actionKeys.js +67 -14
  11. package/dist/actions/actionResponse.js +2 -2
  12. package/dist/actions/index.d.ts +2 -0
  13. package/dist/actions/index.js +2 -0
  14. package/dist/actions/navigationTarget.d.ts +41 -0
  15. package/dist/actions/navigationTarget.js +20 -0
  16. package/dist/actions/recordDelete.d.ts +154 -0
  17. package/dist/actions/recordDelete.js +110 -0
  18. package/dist/adapters/ApiDataSource.d.ts +8 -4
  19. package/dist/adapters/ApiDataSource.js +19 -7
  20. package/dist/adapters/ValueDataSource.d.ts +6 -1
  21. package/dist/adapters/ValueDataSource.js +1090 -98
  22. package/dist/adapters/batchTransaction.d.ts +1 -1
  23. package/dist/adapters/batchTransaction.js +1 -1
  24. package/dist/adapters/resolveDataSource.d.ts +9 -6
  25. package/dist/adapters/resolveDataSource.js +9 -12
  26. package/dist/builder/schema-builder.d.ts +38 -81
  27. package/dist/builder/schema-builder.js +33 -152
  28. package/dist/data-scope/ViewDataProvider.d.ts +8 -25
  29. package/dist/data-scope/ViewDataProvider.js +27 -54
  30. package/dist/data-scope/element-data-source.d.ts +261 -7
  31. package/dist/data-scope/element-data-source.js +342 -52
  32. package/dist/data-scope/index.d.ts +9 -4
  33. package/dist/data-scope/index.js +9 -4
  34. package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
  35. package/dist/evaluator/ExpressionEvaluator.js +184 -12
  36. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  37. package/dist/evaluator/FormulaFunctions.js +166 -27
  38. package/dist/evaluator/deadlineSettled.d.ts +40 -0
  39. package/dist/evaluator/deadlineSettled.js +45 -0
  40. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  41. package/dist/evaluator/declaredPredicate.js +117 -16
  42. package/dist/evaluator/fieldRules.d.ts +88 -4
  43. package/dist/evaluator/fieldRules.js +208 -31
  44. package/dist/evaluator/index.d.ts +2 -0
  45. package/dist/evaluator/index.js +2 -0
  46. package/dist/evaluator/listConditional.d.ts +103 -34
  47. package/dist/evaluator/listConditional.js +127 -50
  48. package/dist/evaluator/optionRules.d.ts +77 -6
  49. package/dist/evaluator/optionRules.js +87 -10
  50. package/dist/evaluator/predicateInput.d.ts +15 -4
  51. package/dist/evaluator/predicateInput.js +35 -2
  52. package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
  53. package/dist/evaluator/rowPredicateCanon.js +65 -115
  54. package/dist/evaluator/subjectPermissions.d.ts +105 -0
  55. package/dist/evaluator/subjectPermissions.js +86 -0
  56. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  57. package/dist/evaluator/unevaluablePredicate.js +70 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/index.js +83 -2
  60. package/dist/protocols/index.d.ts +0 -1
  61. package/dist/protocols/index.js +9 -1
  62. package/dist/registry/Registry.d.ts +382 -34
  63. package/dist/registry/Registry.js +247 -12
  64. package/dist/registry/WidgetRegistry.js +7 -3
  65. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  66. package/dist/registry/html-tier-intrinsics.js +110 -0
  67. package/dist/registry/public-blocks.d.ts +8 -0
  68. package/dist/registry/public-blocks.js +57 -0
  69. package/dist/theme/ThemeEngine.js +1 -1
  70. package/dist/utils/affordanceGrants.d.ts +371 -0
  71. package/dist/utils/affordanceGrants.js +252 -0
  72. package/dist/utils/chart-category-key.d.ts +82 -0
  73. package/dist/utils/chart-category-key.js +84 -0
  74. package/dist/utils/chart-measure-key.d.ts +64 -0
  75. package/dist/utils/chart-measure-key.js +65 -0
  76. package/dist/utils/chart-presentation.d.ts +95 -112
  77. package/dist/utils/chart-presentation.js +169 -104
  78. package/dist/utils/chart-series.d.ts +165 -5
  79. package/dist/utils/chart-series.js +123 -18
  80. package/dist/utils/column-sortability.d.ts +157 -0
  81. package/dist/utils/column-sortability.js +217 -0
  82. package/dist/utils/dashboard-filters.d.ts +38 -6
  83. package/dist/utils/dashboard-filters.js +107 -61
  84. package/dist/utils/dataset-format.d.ts +211 -30
  85. package/dist/utils/dataset-format.js +389 -46
  86. package/dist/utils/date-display.d.ts +346 -0
  87. package/dist/utils/date-display.js +644 -0
  88. package/dist/utils/date-macros.js +7 -0
  89. package/dist/utils/drill-down.d.ts +78 -0
  90. package/dist/utils/drill-down.js +93 -0
  91. package/dist/utils/emptiness.d.ts +96 -0
  92. package/dist/utils/emptiness.js +101 -0
  93. package/dist/utils/expand-fields.d.ts +81 -9
  94. package/dist/utils/expand-fields.js +81 -9
  95. package/dist/utils/extract-records.d.ts +65 -5
  96. package/dist/utils/extract-records.js +65 -8
  97. package/dist/utils/filter-converter.d.ts +158 -6
  98. package/dist/utils/filter-converter.js +1098 -24
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -33
  101. package/dist/utils/grouping-fields.d.ts +61 -0
  102. package/dist/utils/grouping-fields.js +84 -0
  103. package/dist/utils/managedBy.d.ts +3 -3
  104. package/dist/utils/managedBy.js +5 -5
  105. package/dist/utils/native-date-value.d.ts +48 -0
  106. package/dist/utils/native-date-value.js +167 -0
  107. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  108. package/dist/utils/non-grid-row-ceiling.js +141 -0
  109. package/dist/utils/normalize-list-view.d.ts +135 -11
  110. package/dist/utils/normalize-list-view.js +243 -64
  111. package/dist/utils/number-display.d.ts +63 -14
  112. package/dist/utils/number-display.js +34 -12
  113. package/dist/utils/object-aggregate-query.d.ts +113 -0
  114. package/dist/utils/object-aggregate-query.js +50 -0
  115. package/dist/utils/parent-scope.d.ts +123 -0
  116. package/dist/utils/parent-scope.js +140 -0
  117. package/dist/utils/predicate-fields.d.ts +45 -12
  118. package/dist/utils/predicate-fields.js +141 -12
  119. package/dist/utils/record-color.d.ts +75 -0
  120. package/dist/utils/record-color.js +65 -0
  121. package/dist/utils/record-source.d.ts +298 -0
  122. package/dist/utils/record-source.js +331 -0
  123. package/dist/utils/record-title.d.ts +148 -8
  124. package/dist/utils/record-title.js +220 -26
  125. package/dist/utils/reference-keys.d.ts +21 -22
  126. package/dist/utils/reference-keys.js +532 -29
  127. package/dist/utils/sort-query.d.ts +115 -15
  128. package/dist/utils/sort-query.js +240 -13
  129. package/dist/utils/text-comparand.d.ts +92 -0
  130. package/dist/utils/text-comparand.js +99 -0
  131. package/dist/utils/without-denied-fields.d.ts +49 -0
  132. package/dist/utils/without-denied-fields.js +52 -0
  133. package/dist/validation/index.d.ts +8 -4
  134. package/dist/validation/index.js +8 -4
  135. package/dist/validation/schema-validator.d.ts +1 -1
  136. package/dist/validation/schema-validator.js +225 -83
  137. package/dist/validation/server-owned-value.d.ts +5 -1
  138. package/dist/validation/server-owned-value.js +5 -1
  139. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  140. package/dist/validation/validators/object-validation-engine.js +14 -3
  141. package/package.json +8 -9
  142. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  143. package/dist/data-scope/DataScopeManager.js +0 -229
  144. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  145. package/dist/protocols/ResponsiveProtocol.js +0 -158
  146. package/dist/validation/validation-engine.d.ts +0 -88
  147. package/dist/validation/validation-engine.js +0 -434
@@ -0,0 +1,331 @@
1
+ /**
2
+ * ObjectUI — the shared record-source readers: the ruled three-rung ladder
3
+ * (`resolveRecordSourceConfig`) and the object-name it resolves to
4
+ * (`resolveRecordSourceObjectName`)
5
+ * Copyright (c) 2024-present ObjectStack Inc.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */
10
+ /**
11
+ * The object a view block is bound to, resolved ONCE for the whole renderer
12
+ * (`b041b9c0c`).
13
+ *
14
+ * ## The question this answers, and the one it does not
15
+ *
16
+ * There are TWO separately-ruled precedence questions about `objectName`, and
17
+ * they only look like one question when a single reader is asked to answer
18
+ * both:
19
+ *
20
+ * 1. **Which object does this block RESOLVE, at render time, when it carries
21
+ * more than one binding?** — the published three-rung record-source ladder
22
+ * (`data`, then `staticData`, then `objectName`), declared on both faces of
23
+ * the contract (`ObjectMapSchema.objectName` / `ObjectGanttSchema.objectName`
24
+ * in `@object-ui/types`, and the `.describe` on their zod twins — the map
25
+ * twin's reads *"objectName — the THIRD record source `getDataConfig`
26
+ * resolves, after `data` and `staticData`"*, naming `ObjectMap.tsx`'s local
27
+ * wrapper; the gantt twin names `resolveRecordSourceConfig` itself), ruled
28
+ * 2026-09-02 (landed as `77cb489b4`) and pinned by
29
+ * `objectql-record-source-refinement-6939.test.ts`. **That is this
30
+ * function.**
31
+ * 2. **How does `objectName` get POPULATED when it is absent?** — the
32
+ * authoring-time gap-fill in `normalizeListViewSchema` (objectui#7477,
33
+ * ruling B, landed as `00d2fa682`), where an `objectName` already on the schema WINS
34
+ * and the `data` block only fills a gap: *"it can never re-point a binding
35
+ * that already resolves."*
36
+ *
37
+ * The two are NOT merged and neither is re-pointed at the other. Merging them
38
+ * would override a standing maintainer ruling in whichever direction the merged
39
+ * reader happened to pick: at the sites below the binding that already resolves
40
+ * IS `data.object`, so ruling B's own words argue for keeping rung 1 as it is.
41
+ *
42
+ * ## Why `staticData` does not appear here
43
+ *
44
+ * The ladder's second rung wraps inline rows as `{ provider: 'value', items }`,
45
+ * which names no object at all. So for the object-NAME question the three-rung
46
+ * ladder reduces to two rungs — the resolved config's object when it names one,
47
+ * else the schema's own `objectName`, which is what a `value`-backed block
48
+ * still needs for metadata reads, i18n field labels and permission verdicts.
49
+ * Callers pass the ALREADY-RESOLVED config (their
50
+ * {@link resolveRecordSourceConfig} output, direct or through a local wrapper),
51
+ * so rung ordering is settled before this function is reached.
52
+ *
53
+ * ## No lenient rung was added (AGENTS.md #0.1)
54
+ *
55
+ * `ViewDataSchema`'s `object` provider is a `strictObject` carrying exactly
56
+ * `{ provider, object }` with `object` REQUIRED, so `{ provider: 'object' }`
57
+ * without an `object` is off-contract. This reader does not coerce that shape
58
+ * back to `objectName`; the two call sites that used to (`ObjectGrid`'s
59
+ * `'object' in dataConfig` test and `ObjectTree`'s header `?? schema.objectName`
60
+ * tail) keep their own tail at the site, so the collapse changes nothing they
61
+ * resolve today while the shared rung stays contract-strict.
62
+ *
63
+ * @param schema - The block's schema; only `objectName` is read.
64
+ * @param dataConfig - The RESOLVED data config — the caller's own
65
+ * {@link resolveRecordSourceConfig} output, `null` when nothing is bound.
66
+ * @returns The bound object's name, or `undefined` when neither the resolved
67
+ * config nor the schema names one.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const dataConfig = useMemo(() => resolveRecordSourceConfig(schema, 'view-data'), [schema]);
72
+ * const objectName = resolveRecordSourceObjectName(schema, dataConfig);
73
+ * ```
74
+ */
75
+ export function resolveRecordSourceObjectName(schema, dataConfig) {
76
+ return dataConfig?.provider === 'object' ? dataConfig.object : schema?.objectName;
77
+ }
78
+ /**
79
+ * Does the authored `data` match the arm this block's published row declares?
80
+ *
81
+ * Falsy `data` is never a record source — the pre-8348 `if (schema.data)`
82
+ * truthiness test, kept, so `data: null` and `data: undefined` still fall
83
+ * through to `staticData`.
84
+ */
85
+ function authoredDataIsOnTheDeclaredArm(authored, arm) {
86
+ if (!authored)
87
+ return false;
88
+ if (arm === 'array')
89
+ return Array.isArray(authored);
90
+ if (arm === 'view-data')
91
+ return !Array.isArray(authored);
92
+ return true;
93
+ }
94
+ /**
95
+ * The block's record source, resolved from the ruled three-rung ladder
96
+ * (`ce2aaefe1`), with rung 1 judged against the block's own published `data`
97
+ * row (objectui#8348).
98
+ *
99
+ * ## The ruled contract this is the ONE implementation of
100
+ *
101
+ * `data`, then `staticData`, then `objectName` — declared on both faces of the
102
+ * published contract and pinned by
103
+ * `objectql-record-source-refinement-6939.test.ts`. Quoted here from the
104
+ * `ObjectMapSchema` faces, whose `getDataConfig` is `ObjectMap.tsx`'s local
105
+ * wrapper around this function; the gantt and calendar faces name
106
+ * `resolveRecordSourceConfig` instead (objectui#9618):
107
+ *
108
+ * 1. **`data`** — *"Data source configuration. Read FIRST by `getDataConfig`"*,
109
+ * honoured ONLY on the arm `dataArm` names. Returned verbatim, so a config
110
+ * on the declared arm reaches the caller exactly as the author wrote it.
111
+ * 2. **`staticData`** — *"Inline records — read SECOND by `getDataConfig`,
112
+ * wrapped into a `{ provider: value }` config"*.
113
+ * 3. **`objectName`** — *"the THIRD record source `getDataConfig` resolves,
114
+ * after `data` and `staticData`"*, folded to `{ provider: 'object' }`.
115
+ *
116
+ * `null` when none of the three is present — the same "nothing is drawn" signal
117
+ * the zod `requireRecordSource` refinement is written against.
118
+ *
119
+ * This is the PRODUCER whose output {@link resolveRecordSourceObjectName} (the
120
+ * reader `b041b9c0c` published) consumes; that function's docblock describes the same
121
+ * ladder from the consuming end. Five plugins — calendar, gantt, grid, map and
122
+ * tree — each carried a hand-copy of this ladder with no gate holding them
123
+ * together, which is the AGENTS.md #0.1 drift class: a change to the ruled
124
+ * order had five edit sites and nothing noticed a missed one.
125
+ *
126
+ * ## Rung 1 is judged against the block's own row (objectui#8348)
127
+ *
128
+ * ⛔ This docblock used to state, as a fact about the whole ladder, that *"an
129
+ * array under `data` cannot be published"*. That is true of the blocks whose row
130
+ * is `ViewData` and FALSE of `object-calendar`, whose published row
131
+ * (`ComponentPropsMap['object-calendar'].data` on `@objectstack/spec` 17.4.0) is
132
+ * `z.array(z.unknown()).optional()` — *"Pre-fetched records — skips the internal
133
+ * fetch"*. Both directions of that disagreement were live at once: the renderers
134
+ * honoured the `{ provider, items }` object on a block whose row refuses it by
135
+ * kind, and `ObjectGrid` / `ObjectMap` lifted a bare array on blocks whose row
136
+ * refuses THAT by kind. Decision batch #83 settled it — the row decides — and
137
+ * `dataArm` is where each block says which row it has.
138
+ *
139
+ * JUDGED, per block, against that block's `ComponentPropsMap` row in the
140
+ * INSTALLED `@objectstack/spec` — and ⛔ the verdicts are not written down here
141
+ * as facts, because a spec release can move a row (AGENTS.md #9). Each pin
142
+ * below derives the arm from the row at test time and compares it with what the
143
+ * block passes, so a moved row turns it red:
144
+ *
145
+ * - `object-grid` — `'view-data'`; `gridBareArrayDataRefused-8348.test.tsx`,
146
+ * its "the spec row is what all of the above is judged against" test. The
147
+ * row's own description names the refusal: *"the bare-array shortcut is
148
+ * refused — see migration `object-grid-data-view-data-converged`"*.
149
+ * - `object-calendar` — `'array'`; the test of the same name in
150
+ * `ObjectCalendar.dataObjectArmRefused-8348.test.tsx`. The registration
151
+ * publishes the same arm (`{ name: 'data', type: 'array' }`).
152
+ * - `object-map`, `object-gantt`, `object-tree` — `'view-data'`; one
153
+ * `dataArmSpecRow-8348` pin per block, in each plugin's own tests. These
154
+ * three rows are what ruling batch #136 item 3 (Q1-C) had the protocol
155
+ * gain; until they were installable, map and gantt were judged through this
156
+ * repo's own `ObjectMapSchema.data` / `ObjectGanttSchema.data` and tree
157
+ * passed `'undeclared'`. The tree's row declares `data`, so the ruling's
158
+ * 「协议没有的能力删」 clause did not fire for it: it keeps the object arm and
159
+ * loses only the bare-array spelling. Its `staticData` rung is declared by
160
+ * the same row and is unchanged.
161
+ *
162
+ * ## The DECLARATION follows the arm too (`ab856ed30`)
163
+ *
164
+ * The `data` member below is {@link AuthoredRecordSourceData}`<Arm>`, not a flat
165
+ * `ViewData`: the arm decides what a site may DECLARE exactly as it decides
166
+ * what rung 1 HONOURS. Read that type's docblock for why it is arm-indexed
167
+ * rather than a union of the arms, and for the reading that says nothing at
168
+ * runtime moved.
169
+ *
170
+ * ## No lenient rung was added (AGENTS.md #0.1)
171
+ *
172
+ * **Null tolerance** stays out: all five copies dereference `schema` unguarded
173
+ * and would throw on `null`; no site passes one, so no `?.` was added.
174
+ *
175
+ * `ObjectCalendar`'s copy guarded with `'data' in schema && schema.data`. That
176
+ * `in` test was a TYPESCRIPT narrowing device, not a behavioural one: when the
177
+ * property is absent the read yields `undefined`, which is falsy either way, so
178
+ * the guard could never change which rung is taken. The optional-property
179
+ * parameter below admits a schema that declares neither key directly, which is
180
+ * why the guard is gone rather than flattened away. The equivalence itself is
181
+ * pinned, not argued, by `CALENDAR_IN_GUARD` in
182
+ * `record-source-config.behaviourNeutrality-7632.test.ts`.
183
+ *
184
+ * ⛔ This passage used to explain that guard by the PROPS UNION the calendar
185
+ * plugin once carried — `ObjectGridSchema | CalendarSchema`, whose second
186
+ * member declared neither `data` nor `staticData`. objectui#8651 RETIRED that
187
+ * union (`ObjectCalendarComponentProps.schema` is the published
188
+ * `ObjectCalendarSchema` today), so the sentence explained the guard by a type
189
+ * no reader could look up. Rewritten rather than deleted: the guard's history
190
+ * is still why this parameter's members are optional.
191
+ *
192
+ * @param schema - The block's schema; only `data`, `staticData` and
193
+ * `objectName` are read. Its `data` member is typed by the arm — see
194
+ * {@link AuthoredRecordSourceData}.
195
+ * @param dataArm - The arm the CALLING BLOCK's published `data` row declares.
196
+ * Required: there is no repo-wide default, because the answer differs per
197
+ * block and a default is how the second de-facto contract got in.
198
+ * @returns The resolved data config, or `null` when nothing is bound.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * const dataConfig = useMemo(() => resolveRecordSourceConfig(schema, 'view-data'), [schema]);
203
+ * const objectName = resolveRecordSourceObjectName(schema, dataConfig);
204
+ * ```
205
+ */
206
+ export function resolveRecordSourceConfig(schema, dataArm) {
207
+ if (authoredDataIsOnTheDeclaredArm(schema.data, dataArm)) {
208
+ return schema.data;
209
+ }
210
+ if (schema.staticData) {
211
+ return {
212
+ provider: 'value',
213
+ items: schema.staticData,
214
+ };
215
+ }
216
+ if (schema.objectName) {
217
+ return {
218
+ provider: 'object',
219
+ object: schema.objectName,
220
+ };
221
+ }
222
+ return null;
223
+ }
224
+ /**
225
+ * The arm each REGISTERED BLOCK TYPE declares — the one reading available to a
226
+ * caller that cannot be handed the arm as a parameter (objectui#9571).
227
+ *
228
+ * ## Why a type-keyed table exists beside the REQUIRED parameter
229
+ *
230
+ * {@link RecordSourceDataArm}'s docblock states, correctly, why
231
+ * {@link resolveRecordSourceConfig} takes the arm as a required PARAMETER: most
232
+ * of these renderers are registered under two spellings, a node reaches the same
233
+ * component under either, and a table keyed by `type` would answer for one tag
234
+ * and silently miss the other. That argument is about a call site that already
235
+ * has one block in front of it — there a parameter is strictly better, and it
236
+ * stays exactly as it is.
237
+ *
238
+ * `SchemaRenderer` is the one reader that has no such site. It is generic over
239
+ * every registered type and holds nothing but `schema.type`, and ruling
240
+ * objectui#8348 Q2-C (decision batch #136 item 3, maintainer 「同意」) makes it a
241
+ * reader anyway: it must stop spreading an authored `data` key as a React prop
242
+ * for blocks whose published row is the OBJECT arm, because that prop is the
243
+ * carrier the 「8348 以协议为准」 ruling did not otherwise reach. So "which arm
244
+ * does this TYPE declare" has to be askable, and this is the ONE place that
245
+ * answers it.
246
+ *
247
+ * ## The alias hazard is closed by ENUMERATION plus a registry-derived pin
248
+ *
249
+ * Every registered spelling is listed, and the listing is pinned per plugin
250
+ * AGAINST THE REGISTRY rather than against itself: each affected plugin's pin
251
+ * walks the keys its own `index.tsx` actually produced, groups them by the
252
+ * renderer they resolve to, and requires every member of a group to get the
253
+ * same answer here. An alias added without a row goes red there instead of
254
+ * quietly answering `'undeclared'` and keeping the prop seat.
255
+ *
256
+ * ⚠️ ONE `register()` CALL PRODUCES SEVERAL KEYS, and a row must be written for
257
+ * each of them — `SchemaRenderer` looks this table up with the raw
258
+ * `schema.type`, which is whatever spelling the author wrote. A registration
259
+ * with `namespace: 'view'` is reachable as `view:calendar` AND as bare `calendar`; one
260
+ * with `skipFallback` is reachable ONLY under its namespaced key. MEASURED via
261
+ * `ComponentRegistry.getAllTypes()` with the five plugins loaded, grouped by
262
+ * the renderer each key resolves to.
263
+ *
264
+ * ⛔ `grid` IS NOT A GRID KEY, and this is the trap the registry-derived pin
265
+ * caught on its first run. `plugin-grid` registers its `view` alias with
266
+ * `skipFallback: true` precisely so the bare key stays with
267
+ * `@object-ui/components`' LAYOUT grid container (`ui:grid`) — so `object-grid`
268
+ * resolves to `view:grid`, never to `grid`. A row for `grid` here would strip
269
+ * `data` from a layout container, which declares no `data` row at all.
270
+ *
271
+ * ## The rows are the call sites' literals — ⛔ not a second measurement
272
+ *
273
+ * Each value below is the literal its block already passes to
274
+ * {@link resolveRecordSourceConfig}, and each of those literals carries its own
275
+ * per-block measurement in a comment at that call site. ⛔ Do not re-derive the
276
+ * arm here and ⛔ do not let the two disagree: this table transports an answer,
277
+ * it does not compute one.
278
+ *
279
+ * - `ObjectGrid.tsx`'s `getDataConfig` — `'view-data'`.
280
+ * - `ObjectMap.tsx`'s `getDataConfig` — `'view-data'`. One block spelling
281
+ * only: the bare `map` key (and with it `view:map`) is retired
282
+ * (objectui#10393).
283
+ * - `ObjectGantt.tsx`'s `rawDataConfig` — `'view-data'`. One block spelling
284
+ * only: the bare `gantt` key is retired (objectui#8008).
285
+ * - `ObjectCalendar.tsx`'s `dataConfig` — `'array'`.
286
+ * - `ObjectTree.tsx`'s `dataConfig` — `'view-data'`. One block spelling only
287
+ * (`object-tree` under the `plugin-tree` namespace): the bare `tree` key
288
+ * (and with it `view:tree`) is retired (objectui#10859 batch 8). These rows
289
+ * used to read `'undeclared'` and were only
290
+ * documentary, because no published face declared a `data` row for the
291
+ * block; `ComponentPropsMap['object-tree']` now does (objectui#8348, ruling
292
+ * batch #136 item 3, Q1-C), so they DECIDE: an authored `data` on a tree
293
+ * node no longer takes a prop seat either. The registry-derived pin is
294
+ * `ObjectTree.dataArmSpecRow-8348.test.tsx`.
295
+ */
296
+ const RECORD_SOURCE_DATA_ARM_BY_TYPE = Object.freeze({
297
+ // — object arm: `ViewData`, a bare array under `data` is off the row —
298
+ 'object-grid': 'view-data',
299
+ 'plugin-grid:object-grid': 'view-data',
300
+ 'view:grid': 'view-data',
301
+ 'object-map': 'view-data',
302
+ 'plugin-map:object-map': 'view-data',
303
+ 'object-gantt': 'view-data',
304
+ 'plugin-gantt:object-gantt': 'view-data',
305
+ 'object-tree': 'view-data',
306
+ 'plugin-tree:object-tree': 'view-data',
307
+ // — array arm: `z.array(...)`, pre-fetched records —
308
+ 'object-calendar': 'array',
309
+ 'plugin-calendar:object-calendar': 'array',
310
+ 'view:calendar': 'array',
311
+ calendar: 'array',
312
+ });
313
+ /**
314
+ * Which `data` arm the block registered under `type` declares.
315
+ *
316
+ * `'undeclared'` for every type not listed above — the same verdict the ladder
317
+ * gives a block no published face declares a `data` row for, and the same
318
+ * behaviour those types have today. That default is deliberately the
319
+ * NO-CHANGE direction: a caller acting on this reading (today:
320
+ * `SchemaRenderer`'s props spread) leaves an unlisted type exactly as it was,
321
+ * so a block that has never been measured is never silently re-decided by this
322
+ * table's mere existence.
323
+ *
324
+ * @param type - The node's registry key, either spelling.
325
+ * @returns The arm that type's published `data` row declares.
326
+ */
327
+ export function recordSourceDataArmForType(type) {
328
+ if (typeof type !== 'string')
329
+ return 'undeclared';
330
+ return RECORD_SOURCE_DATA_ARM_BY_TYPE[type] ?? 'undeclared';
331
+ }
@@ -15,10 +15,20 @@
15
15
  * supported. Dotted paths (`{account.name}`) walk `$expanded` lookup objects; a
16
16
  * placeholder that resolves to a whole object is reduced to its display name.
17
17
  *
18
- * Empty placeholders (missing / null / empty fields) are stripped along with
19
- * any orphan separator they leave behind, so `"{full_name} - {company}"`
20
- * evaluated against `{ company: "Acme" }` yields `"Acme"`, not `" - Acme"`.
21
- * Returns an empty string when no placeholder resolved.
18
+ * Empty placeholders (missing / null / empty / whitespace-only fields) are
19
+ * stripped along with any orphan separator they leave behind, so
20
+ * `"{full_name} - {company}"` evaluated against `{ company: "Acme" }` yields
21
+ * `"Acme"`, not `" - Acme"`. Returns an empty string when no placeholder
22
+ * resolved.
23
+ *
24
+ * What counts as "empty" is not decided here (objectui#10446). Each
25
+ * placeholder's value goes through {@link displayValueOf}, the body of
26
+ * {@link recordDisplayValueAt}: trim, then empty. This function used to test
27
+ * the raw value against `null` / `undefined` / `''` on its own, so a
28
+ * whitespace-only value counted as RESOLVED. The later whitespace collapse
29
+ * then left its separator dangling: `{contract_no} - {name}` with a blank
30
+ * `name` rendered `HT-2026-003 -`, while every value-keyed rung of
31
+ * {@link getRecordDisplayName} judged the same value empty.
22
32
  */
23
33
  export declare function formatTitleTemplate(titleFormat: string | {
24
34
  source?: string;
@@ -49,6 +59,135 @@ export declare function isTitleEligibleField(def: any): boolean;
49
59
  * when `objectDef` is a non-null object (the WeakMap key requirement).
50
60
  */
51
61
  export declare function deriveTitleField(objectDef: any): string | undefined;
62
+ /**
63
+ * The declared name-field pointer, read exactly as {@link getRecordDisplayName}
64
+ * reads it: the canonical `nameField` (ADR-0079 Phase 2), then its deprecated
65
+ * `displayNameField` / `NAME_FIELD_KEY` aliases.
66
+ *
67
+ * One spelling, every reader. Inside this module that is the value-space
68
+ * resolver and the name-space {@link resolveNameField}. Re-typing the `??`
69
+ * chain at another reader is how two readers drift into disagreeing about
70
+ * which field titles an object. No new alias is read here; this is the
71
+ * existing ladder, extracted.
72
+ *
73
+ * Exported (objectui#9436) for callers that must rank the DECLARED pointer on
74
+ * its own, apart from the type-aware derivation that {@link resolveNameField}
75
+ * folds in after it. The record-page H1 needs exactly that: ADR-0079 puts the
76
+ * declared pointer (steps 1+2) above the legacy `titleFormat` (step 3), and the
77
+ * derivation (step 4) below it, so a caller that interleaves its own
78
+ * `titleFormat` rendering has to read the two halves separately. Read the value
79
+ * as `recordDisplayValueAt(record, declaredNameField(objectDef))`, which is how
80
+ * {@link getRecordDisplayName} reads steps 1+2.
81
+ *
82
+ * Returns the pointer exactly as declared, or `undefined` when none is. It
83
+ * never derives: an object that declares nothing answers `undefined` even when
84
+ * its `fields` would derive a title field.
85
+ */
86
+ export declare function declaredNameField(objectDef: any): any;
87
+ /**
88
+ * WHICH FIELD titles this object — the name-space twin of
89
+ * {@link getRecordDisplayName}, which answers what that field SAYS on one
90
+ * record. Same ladder, minus the rungs that only exist per record:
91
+ *
92
+ * 1+2. the declared pointer ({@link declaredNameField});
93
+ * 4. else the type-aware derivation ({@link deriveTitleField}).
94
+ *
95
+ * The two omissions are deliberate, not oversights. `options.titleField`
96
+ * (step 0) belongs to one view, not to the object. `titleFormat` (step 3) is a
97
+ * render-only template, not a field name — there is no column, no sort key and
98
+ * no `$select` entry to be had from it, which is the same reason ADR-0079
99
+ * demoted it. A caller that wants a title for a `titleFormat`-only object wants
100
+ * {@link getRecordDisplayName}, per record.
101
+ *
102
+ * Deliberately does NOT require the answer to exist in `objectDef.fields`: in
103
+ * value-space the declared pointer is read off the RECORD, so demanding a field
104
+ * def here would make the two resolvers disagree. A caller that needs a
105
+ * RENDERABLE column checks presence itself — see {@link leadWithNameField}.
106
+ *
107
+ * Returns `undefined` when nothing declares or derives a name field.
108
+ */
109
+ export declare function resolveNameField(objectDef: any): string | undefined;
110
+ /**
111
+ * Put the object's name field at the FRONT of a **synthesized** default column
112
+ * list, so a list no author ever configured still lets you tell its rows apart.
113
+ *
114
+ * Why this exists (objectui#7245): the three faces that synthesize default list
115
+ * columns each took a declared `highlightFields` verbatim. `highlightFields` is
116
+ * the ADR-0085 "most important fields" role, and its canonical consumer — the
117
+ * detail-page highlight strip — deliberately *excludes* the title field,
118
+ * because the strip sits directly under the page H1 that already shows it. So
119
+ * well-authored metadata routinely omits the name from `highlightFields`: the
120
+ * showcase `showcase_account` declares `["status", "industry", "annual_revenue"]`
121
+ * against `nameField: "name"`, and its default grid rendered 14 rows with no
122
+ * name column and nothing to distinguish them.
123
+ *
124
+ * A list has no H1 to lean on, so the same role needs the opposite treatment
125
+ * here. This is not a new convention: `deriveLookupColumns` in `@object-ui/fields`
126
+ * already leads its picker columns with the display field and filters it out of
127
+ * the declared list — the record-picker equivalent of this problem, solved the
128
+ * same way. This makes the list faces agree with it.
129
+ *
130
+ * MOVES the name field rather than only prepending a missing one: an author who
131
+ * lists it third still gets it first, which is what "the column that identifies
132
+ * the row" means. Order among the remaining columns is untouched.
133
+ *
134
+ * NOT for author-declared column lists. A view that declares `columns` said what
135
+ * it wants; overriding that would be exactly the renderer-side second-guessing
136
+ * AGENTS.md Commandment #0.1 rules out. Every caller passes a list it
137
+ * synthesized itself.
138
+ *
139
+ * Three cases return `columns` untouched, each guarding a rule that already
140
+ * governs these column lists:
141
+ *
142
+ * - **No field def** (nothing resolved, or the pointer names a field the object
143
+ * does not carry) — a synthesized list must never fabricate a column the
144
+ * object cannot render.
145
+ * - **`hidden: true`** — the author said don't show this field. That outranks
146
+ * the fact that it also titles the record; every one of these faces already
147
+ * drops hidden fields from its own walk.
148
+ * - **DERIVED onto a system-managed field.** {@link deriveTitleField} filters
149
+ * by TYPE only, so on an object with no name-ish business field it can land
150
+ * on a framework-injected column — and leading a default list with a raw id
151
+ * is precisely the regression objectui#2702 / #2777 fixed. A *declared*
152
+ * `nameField` is exempt: `sys_migration` really does point at `id`, and an
153
+ * author's explicit designation is not a heuristic misfire.
154
+ */
155
+ export declare function leadWithNameField(objectDef: any, columns: string[]): string[];
156
+ /**
157
+ * Read a non-empty string-ish display value off a record at `field`, else
158
+ * `undefined`.
159
+ *
160
+ * THE definition of "this record has a value here" — the question every
161
+ * value-keyed rung of {@link getRecordDisplayName} asks, and the reason a
162
+ * whitespace-only field is not a title.
163
+ *
164
+ * ## Why this is exported (objectui#8350)
165
+ *
166
+ * A second reader existed. `record:details`' dedupe ladder in
167
+ * `@object-ui/plugin-detail` asks the same question about the same record for
168
+ * the same field, to decide which body-grid row duplicates the page H1 — and it
169
+ * asked it with a hand-written raw `undefined` / `null` / `''` test, which a
170
+ * whitespace-only value passes and this one does not. So for a record whose
171
+ * title field held only spaces the two halves disagreed about whether that
172
+ * field had a value at all: the H1 walked on to the next rung, while the grid
173
+ * hid the row anyway — a field vanished to deduplicate against a heading that
174
+ * never showed it. The ladder now calls THIS. One authority, not two
175
+ * implementations that agree today.
176
+ *
177
+ * ⛔ Do not re-spell this test at a call site, and do not "just add a trim"
178
+ * there: three things beyond the trim are part of the definition, and a second
179
+ * implementation drifts on each of them independently —
180
+ * - an expanded/embedded reference object resolves through its Salesforce-
181
+ * style display chain and is EMPTY when that yields nothing (a bare
182
+ * `{ id }` lookup payload is not a title);
183
+ * - non-strings are stringified, so `0` and `false` ARE values, not blanks;
184
+ * - `null` and `undefined` are empty, and only those two.
185
+ *
186
+ * The rule itself is {@link displayValueOf}, which {@link formatTitleTemplate}
187
+ * applies to each placeholder value (objectui#10446). So a `titleFormat`
188
+ * placeholder is empty exactly when this function would call its field empty.
189
+ */
190
+ export declare function recordDisplayValueAt(record: any, field: string | undefined): string | undefined;
52
191
  export interface RecordDisplayNameOptions {
53
192
  /**
54
193
  * Optional explicit title field (e.g. a view's `titleField` / gallery
@@ -61,7 +200,7 @@ export interface RecordDisplayNameOptions {
61
200
  fallback?: string;
62
201
  /**
63
202
  * Whether to probe standard name-ish keys (`name`/`title`/…) directly on the
64
- * record when `objectDef.fields` is absent (precedence step 3b). Defaults to
203
+ * record when `objectDef.fields` is absent (precedence step 4b). Defaults to
65
204
  * `true`. Callers that have a better generic fallback to interleave (e.g. the
66
205
  * detail header prefers `schema.title`, the object label, over a guessed
67
206
  * record key) pass `false` so this step is skipped and the resolver returns
@@ -74,9 +213,10 @@ export interface RecordDisplayNameOptions {
74
213
  *
75
214
  * Precedence (ADR-0079 Phase 2 makes the object's declared field canonical and
76
215
  * de-prioritizes the legacy `titleFormat` template):
77
- * 0. `options.titleField`, then `objectDef.titleField` (when provided &
78
- * non-empty on the record) — lets a view / object keep its author-chosen
79
- * title field.
216
+ * 0. `options.titleField` (when provided & non-empty on the record) — lets
217
+ * a view keep its author-chosen title field. The object-level
218
+ * `objectDef.titleField` leg was removed in objectui#6531: the spec's
219
+ * object schema rejects that key, so nothing could produce it.
80
220
  * 1. `objectDef.nameField` — the NEW canonical record-title pointer
81
221
  * (ADR-0079). A field name the server can return and query.
82
222
  * 2. `objectDef.displayNameField` / `objectDef.NAME_FIELD_KEY` — the