@object-ui/core 17.6.0 → 17.7.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 (134) hide show
  1. package/CHANGELOG.md +6610 -0
  2. package/README.md +101 -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 +288 -27
  6. package/dist/actions/ActionRunner.js +443 -63
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +9 -7
  9. package/dist/actions/actionKeys.js +57 -14
  10. package/dist/actions/actionResponse.js +2 -2
  11. package/dist/actions/index.d.ts +1 -0
  12. package/dist/actions/index.js +1 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/adapters/ValueDataSource.d.ts +6 -1
  16. package/dist/adapters/ValueDataSource.js +1090 -98
  17. package/dist/adapters/batchTransaction.d.ts +1 -1
  18. package/dist/adapters/batchTransaction.js +1 -1
  19. package/dist/builder/schema-builder.d.ts +38 -81
  20. package/dist/builder/schema-builder.js +33 -152
  21. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  22. package/dist/data-scope/ViewDataProvider.js +21 -1
  23. package/dist/data-scope/element-data-source.d.ts +261 -7
  24. package/dist/data-scope/element-data-source.js +342 -52
  25. package/dist/data-scope/index.d.ts +9 -4
  26. package/dist/data-scope/index.js +9 -4
  27. package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
  28. package/dist/evaluator/ExpressionEvaluator.js +184 -12
  29. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  30. package/dist/evaluator/FormulaFunctions.js +166 -27
  31. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  32. package/dist/evaluator/declaredPredicate.js +117 -16
  33. package/dist/evaluator/fieldRules.d.ts +88 -4
  34. package/dist/evaluator/fieldRules.js +203 -31
  35. package/dist/evaluator/index.d.ts +1 -0
  36. package/dist/evaluator/index.js +1 -0
  37. package/dist/evaluator/listConditional.d.ts +103 -34
  38. package/dist/evaluator/listConditional.js +127 -50
  39. package/dist/evaluator/optionRules.d.ts +77 -6
  40. package/dist/evaluator/optionRules.js +87 -10
  41. package/dist/evaluator/predicateInput.d.ts +15 -4
  42. package/dist/evaluator/predicateInput.js +35 -2
  43. package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
  44. package/dist/evaluator/rowPredicateCanon.js +65 -115
  45. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  46. package/dist/evaluator/subjectPermissions.js +44 -0
  47. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  48. package/dist/evaluator/unevaluablePredicate.js +70 -0
  49. package/dist/index.d.ts +14 -0
  50. package/dist/index.js +80 -2
  51. package/dist/protocols/index.d.ts +0 -1
  52. package/dist/protocols/index.js +9 -1
  53. package/dist/registry/Registry.d.ts +358 -31
  54. package/dist/registry/Registry.js +263 -11
  55. package/dist/registry/WidgetRegistry.js +7 -3
  56. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  57. package/dist/registry/html-tier-intrinsics.js +110 -0
  58. package/dist/registry/public-blocks.d.ts +8 -0
  59. package/dist/registry/public-blocks.js +57 -0
  60. package/dist/theme/ThemeEngine.js +1 -1
  61. package/dist/utils/chart-category-key.d.ts +82 -0
  62. package/dist/utils/chart-category-key.js +84 -0
  63. package/dist/utils/chart-measure-key.d.ts +64 -0
  64. package/dist/utils/chart-measure-key.js +65 -0
  65. package/dist/utils/chart-presentation.d.ts +95 -112
  66. package/dist/utils/chart-presentation.js +169 -104
  67. package/dist/utils/chart-series.d.ts +131 -5
  68. package/dist/utils/chart-series.js +98 -16
  69. package/dist/utils/column-sortability.d.ts +157 -0
  70. package/dist/utils/column-sortability.js +217 -0
  71. package/dist/utils/dashboard-filters.d.ts +38 -6
  72. package/dist/utils/dashboard-filters.js +107 -61
  73. package/dist/utils/dataset-format.d.ts +211 -30
  74. package/dist/utils/dataset-format.js +389 -46
  75. package/dist/utils/date-display.d.ts +354 -0
  76. package/dist/utils/date-display.js +463 -0
  77. package/dist/utils/date-macros.js +7 -0
  78. package/dist/utils/drill-down.d.ts +78 -0
  79. package/dist/utils/drill-down.js +93 -0
  80. package/dist/utils/emptiness.d.ts +96 -0
  81. package/dist/utils/emptiness.js +101 -0
  82. package/dist/utils/expand-fields.d.ts +76 -6
  83. package/dist/utils/expand-fields.js +76 -6
  84. package/dist/utils/extract-records.d.ts +65 -5
  85. package/dist/utils/extract-records.js +65 -8
  86. package/dist/utils/filter-converter.d.ts +152 -4
  87. package/dist/utils/filter-converter.js +1092 -22
  88. package/dist/utils/filter-tokens.d.ts +49 -5
  89. package/dist/utils/filter-tokens.js +105 -33
  90. package/dist/utils/grouping-fields.d.ts +61 -0
  91. package/dist/utils/grouping-fields.js +84 -0
  92. package/dist/utils/managedBy.d.ts +3 -3
  93. package/dist/utils/managedBy.js +5 -5
  94. package/dist/utils/native-date-value.d.ts +48 -0
  95. package/dist/utils/native-date-value.js +167 -0
  96. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  97. package/dist/utils/non-grid-row-ceiling.js +141 -0
  98. package/dist/utils/normalize-list-view.d.ts +121 -1
  99. package/dist/utils/normalize-list-view.js +186 -6
  100. package/dist/utils/number-display.d.ts +63 -14
  101. package/dist/utils/number-display.js +34 -12
  102. package/dist/utils/object-aggregate-query.d.ts +113 -0
  103. package/dist/utils/object-aggregate-query.js +50 -0
  104. package/dist/utils/parent-scope.d.ts +123 -0
  105. package/dist/utils/parent-scope.js +140 -0
  106. package/dist/utils/predicate-fields.d.ts +45 -12
  107. package/dist/utils/predicate-fields.js +137 -12
  108. package/dist/utils/record-color.d.ts +75 -0
  109. package/dist/utils/record-color.js +65 -0
  110. package/dist/utils/record-source.d.ts +298 -0
  111. package/dist/utils/record-source.js +331 -0
  112. package/dist/utils/record-title.d.ts +148 -8
  113. package/dist/utils/record-title.js +220 -26
  114. package/dist/utils/reference-keys.d.ts +21 -22
  115. package/dist/utils/reference-keys.js +532 -29
  116. package/dist/utils/sort-query.d.ts +115 -15
  117. package/dist/utils/sort-query.js +240 -13
  118. package/dist/utils/text-comparand.d.ts +92 -0
  119. package/dist/utils/text-comparand.js +99 -0
  120. package/dist/utils/without-denied-fields.d.ts +49 -0
  121. package/dist/utils/without-denied-fields.js +52 -0
  122. package/dist/validation/index.d.ts +8 -4
  123. package/dist/validation/index.js +8 -4
  124. package/dist/validation/schema-validator.d.ts +1 -1
  125. package/dist/validation/schema-validator.js +200 -82
  126. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  127. package/dist/validation/validators/object-validation-engine.js +14 -3
  128. package/package.json +8 -9
  129. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  130. package/dist/data-scope/DataScopeManager.js +0 -229
  131. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  132. package/dist/protocols/ResponsiveProtocol.js +0 -158
  133. package/dist/validation/validation-engine.d.ts +0 -88
  134. package/dist/validation/validation-engine.js +0 -434
@@ -0,0 +1,96 @@
1
+ /**
2
+ * ObjectUI — the shared emptiness floor
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * THE floor under "is this value empty" (objectui#8496 — director seat,
10
+ * decision batch #86, 2026-09-08, option B).
11
+ *
12
+ * Exactly four members, and it never grows past them:
13
+ *
14
+ * `null` · `undefined` · the empty string · the empty array
15
+ *
16
+ * ## What a floor IS, and what it is not
17
+ *
18
+ * It is the WEAKEST claim the surfaces below it can all make — not the answer
19
+ * any one of them gives. A caller does one of two things with it, and both are
20
+ * legitimate:
21
+ *
22
+ * - **extends** it — `isEmptyValue(v) || <its own clause>` — when its surface
23
+ * calls MORE things empty (a grid trims whitespace; a boolean column calls
24
+ * every non-boolean empty);
25
+ * - **declines a member** in the open — `isEmptyValue(v) && !Array.isArray(v)`
26
+ * — when its surface has MEASURED that member to be a value there (a `json`
27
+ * cell draws the two-character literal `[]` on purpose, objectui#8474).
28
+ *
29
+ * What is NOT legitimate is a sixth private re-spelling of these four members.
30
+ * That is the defect this function exists to close: `plugin-detail`'s
31
+ * `hasCellValue`, `RelatedList`'s `isValueEmpty`, `ObjectGallery`'s and
32
+ * `ObjectKanban`'s inline guards and the guard idioms across
33
+ * `@object-ui/fields`' cell renderers each grew their own copy, and
34
+ * objectui#8481 was the THIRD rediscovery of the same hole — objectui#8474 and
35
+ * objectui#8459 had each closed it at their own door first. A copy that agrees
36
+ * today stops agreeing; one definition cannot.
37
+ *
38
+ * ## ⛔ The floor never grows past those four members
39
+ *
40
+ * The ruling fixed the member list, and every candidate fifth member is a
41
+ * measured disagreement rather than an oversight:
42
+ *
43
+ * - **whitespace-only strings.** `' '` is EMPTY on `record:details` and in
44
+ * `RelatedList` (objectui#8350 measured the damage a blank cell does there)
45
+ * and a VALUE on the gallery, the kanban and the shared renderers. Both are
46
+ * right for their surface, so the trim is an EXTENSION, not a member.
47
+ * - **`{}`.** Measured as a VALUE and pinned (objectui#8474): a populated or
48
+ * empty object literal is handed to a type-aware renderer that draws it, and
49
+ * the shape that would sweep it in — `Object.keys(v).length === 0` — is also
50
+ * true of `new Date(0)`, of a populated `Map`, of a populated `Set` and of
51
+ * any class instance whose state sits behind getters.
52
+ * - **`0` / `false`.** Values everywhere. `BooleanCellRenderer` keeping
53
+ * `false` a value is the pinned case (objectui#8582).
54
+ *
55
+ * ## Why `@object-ui/core` and not `@object-ui/types`
56
+ *
57
+ * It is a runtime predicate, not a protocol type, so it belongs in the engine.
58
+ * The ruling made that conditional on a measurement — `@object-ui/fields` is
59
+ * the lowest consumer, and if it did not already depend on `core` the floor
60
+ * would have had to fall back to `types`. Measured on the implementing branch:
61
+ * `@object-ui/fields`' `package.json` lists `@object-ui/core` in
62
+ * `dependencies`, and its barrel already imports from it. No new dependency
63
+ * edge is created by this file, in either direction — `core` reaches no
64
+ * consumer, which is why exporting the helper from `@object-ui/fields` instead
65
+ * (option C) was refused: the gallery and the kanban would then import a
66
+ * `fields` helper to decide whether to call a `fields` renderer.
67
+ *
68
+ * ## "Empty" is two questions; this floor answers the half both share
69
+ *
70
+ * objectui#8496's later evidence (comment 5603203484) measured that the word
71
+ * has split in two on this codebase: SCALAR-MISSING (`EmptyValue`, the em-dash
72
+ * affordance whose accessible name is fixed) and COLLECTION-EMPTY
73
+ * (`EmptyDescription`, an author's own sentence). The floor serves both and
74
+ * does not have to choose: its four members ARE two scalar-missing members,
75
+ * one blank scalar and one empty collection, and no call site asks a boolean to
76
+ * tell those apart — each one knows statically which affordance it is drawing.
77
+ * ⛔ So this function is deliberately NOT the place to grow a second axis. Which
78
+ * COMPONENT states the emptiness is a different question, carried by
79
+ * objectui#8570 / objectui#8526 / objectui#8507.
80
+ *
81
+ * ## Readers
82
+ *
83
+ * `isOptionGroupGated` / `isValueStillOffered` here in `core` (this function's
84
+ * origin: it was written privately in `evaluator/optionRules.ts`, byte-for-byte
85
+ * these four members, before the ruling promoted it); `hasCellValue` and
86
+ * `RelatedList.isValueEmpty` in `@object-ui/plugin-detail`; `ObjectGallery`'s
87
+ * card-field row filter; `ObjectKanban`'s card-field loop; and the cell-renderer
88
+ * guards in `@object-ui/fields`.
89
+ *
90
+ * The extensions and the declensions are pinned — the assertion that each
91
+ * surface still answers DIFFERENTLY from the floor, not merely that the floor
92
+ * works — in `__tests__/emptiness-floor-8496.test.ts` here and in
93
+ * `emptinessFloorExtensions-8496.test.tsx` in `@object-ui/fields` and
94
+ * `@object-ui/plugin-detail`.
95
+ */
96
+ export declare function isEmptyValue(value: unknown): boolean;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ObjectUI — the shared emptiness floor
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * THE floor under "is this value empty" (objectui#8496 — director seat,
10
+ * decision batch #86, 2026-09-08, option B).
11
+ *
12
+ * Exactly four members, and it never grows past them:
13
+ *
14
+ * `null` · `undefined` · the empty string · the empty array
15
+ *
16
+ * ## What a floor IS, and what it is not
17
+ *
18
+ * It is the WEAKEST claim the surfaces below it can all make — not the answer
19
+ * any one of them gives. A caller does one of two things with it, and both are
20
+ * legitimate:
21
+ *
22
+ * - **extends** it — `isEmptyValue(v) || <its own clause>` — when its surface
23
+ * calls MORE things empty (a grid trims whitespace; a boolean column calls
24
+ * every non-boolean empty);
25
+ * - **declines a member** in the open — `isEmptyValue(v) && !Array.isArray(v)`
26
+ * — when its surface has MEASURED that member to be a value there (a `json`
27
+ * cell draws the two-character literal `[]` on purpose, objectui#8474).
28
+ *
29
+ * What is NOT legitimate is a sixth private re-spelling of these four members.
30
+ * That is the defect this function exists to close: `plugin-detail`'s
31
+ * `hasCellValue`, `RelatedList`'s `isValueEmpty`, `ObjectGallery`'s and
32
+ * `ObjectKanban`'s inline guards and the guard idioms across
33
+ * `@object-ui/fields`' cell renderers each grew their own copy, and
34
+ * objectui#8481 was the THIRD rediscovery of the same hole — objectui#8474 and
35
+ * objectui#8459 had each closed it at their own door first. A copy that agrees
36
+ * today stops agreeing; one definition cannot.
37
+ *
38
+ * ## ⛔ The floor never grows past those four members
39
+ *
40
+ * The ruling fixed the member list, and every candidate fifth member is a
41
+ * measured disagreement rather than an oversight:
42
+ *
43
+ * - **whitespace-only strings.** `' '` is EMPTY on `record:details` and in
44
+ * `RelatedList` (objectui#8350 measured the damage a blank cell does there)
45
+ * and a VALUE on the gallery, the kanban and the shared renderers. Both are
46
+ * right for their surface, so the trim is an EXTENSION, not a member.
47
+ * - **`{}`.** Measured as a VALUE and pinned (objectui#8474): a populated or
48
+ * empty object literal is handed to a type-aware renderer that draws it, and
49
+ * the shape that would sweep it in — `Object.keys(v).length === 0` — is also
50
+ * true of `new Date(0)`, of a populated `Map`, of a populated `Set` and of
51
+ * any class instance whose state sits behind getters.
52
+ * - **`0` / `false`.** Values everywhere. `BooleanCellRenderer` keeping
53
+ * `false` a value is the pinned case (objectui#8582).
54
+ *
55
+ * ## Why `@object-ui/core` and not `@object-ui/types`
56
+ *
57
+ * It is a runtime predicate, not a protocol type, so it belongs in the engine.
58
+ * The ruling made that conditional on a measurement — `@object-ui/fields` is
59
+ * the lowest consumer, and if it did not already depend on `core` the floor
60
+ * would have had to fall back to `types`. Measured on the implementing branch:
61
+ * `@object-ui/fields`' `package.json` lists `@object-ui/core` in
62
+ * `dependencies`, and its barrel already imports from it. No new dependency
63
+ * edge is created by this file, in either direction — `core` reaches no
64
+ * consumer, which is why exporting the helper from `@object-ui/fields` instead
65
+ * (option C) was refused: the gallery and the kanban would then import a
66
+ * `fields` helper to decide whether to call a `fields` renderer.
67
+ *
68
+ * ## "Empty" is two questions; this floor answers the half both share
69
+ *
70
+ * objectui#8496's later evidence (comment 5603203484) measured that the word
71
+ * has split in two on this codebase: SCALAR-MISSING (`EmptyValue`, the em-dash
72
+ * affordance whose accessible name is fixed) and COLLECTION-EMPTY
73
+ * (`EmptyDescription`, an author's own sentence). The floor serves both and
74
+ * does not have to choose: its four members ARE two scalar-missing members,
75
+ * one blank scalar and one empty collection, and no call site asks a boolean to
76
+ * tell those apart — each one knows statically which affordance it is drawing.
77
+ * ⛔ So this function is deliberately NOT the place to grow a second axis. Which
78
+ * COMPONENT states the emptiness is a different question, carried by
79
+ * objectui#8570 / objectui#8526 / objectui#8507.
80
+ *
81
+ * ## Readers
82
+ *
83
+ * `isOptionGroupGated` / `isValueStillOffered` here in `core` (this function's
84
+ * origin: it was written privately in `evaluator/optionRules.ts`, byte-for-byte
85
+ * these four members, before the ruling promoted it); `hasCellValue` and
86
+ * `RelatedList.isValueEmpty` in `@object-ui/plugin-detail`; `ObjectGallery`'s
87
+ * card-field row filter; `ObjectKanban`'s card-field loop; and the cell-renderer
88
+ * guards in `@object-ui/fields`.
89
+ *
90
+ * The extensions and the declensions are pinned — the assertion that each
91
+ * surface still answers DIFFERENTLY from the floor, not merely that the floor
92
+ * works — in `__tests__/emptiness-floor-8496.test.ts` here and in
93
+ * `emptinessFloorExtensions-8496.test.tsx` in `@object-ui/fields` and
94
+ * `@object-ui/plugin-detail`.
95
+ */
96
+ export function isEmptyValue(value) {
97
+ return (value === undefined ||
98
+ value === null ||
99
+ value === '' ||
100
+ (Array.isArray(value) && value.length === 0));
101
+ }
@@ -7,14 +7,52 @@
7
7
  */
8
8
  /**
9
9
  * Relational ("reference-bearing") field types whose stored value is a foreign
10
- * key into another object — and which therefore benefit from `$expand` so a
11
- * list / grid / detail cell can render the related record's display name
12
- * instead of a bare id placeholder ("—").
10
+ * key into another object — and which therefore benefit from `$expand`, so a
11
+ * response carries the related record instead of only its foreign key.
13
12
  *
14
13
  * `user` is a lookup specialised to `sys_user`: it carries the same `reference`
15
14
  * + id storage and the server resolves it through the same expand path as
16
15
  * `lookup` / `master_detail`, so a `user` column that is NOT requested for
17
- * expansion comes back as a raw user id and renders as "—" (objectui#2032).
16
+ * expansion comes back as a raw user id (objectui#2032). What a CELL then
17
+ * prints for that id is deliberately not stated here — see the next section.
18
+ *
19
+ * ## No rendering claim is made here, on purpose — objectui#8693
20
+ *
21
+ * Both paragraphs above used to make one: the `user` sentence ended "…and
22
+ * renders as '—'", and the opening one called the unexpanded cell "a bare id
23
+ * placeholder ('—')". Measured on `da5e4f69e` by resolving each family through
24
+ * `getCellRenderer` and rendering three unexpanded primitives:
25
+ *
26
+ * | unexpanded value | `lookup` / `master_detail` / `tree` | `user` |
27
+ * |----------------------|-------------------------------------|--------------------|
28
+ * | `'u_1'` | `u_1`, plain truncated span | `u_1`, same span |
29
+ * | `'01HQZX9K2M4N6P8R'` | muted `—` | the raw ULID |
30
+ * | `'Ada Lovelace'` | `Ada Lovelace`, plain span | the raw string |
31
+ *
32
+ * Wrong twice over. `user` never drew the em-dash at all — `UserCellRenderer`
33
+ * has no resolver, no `useLookupName`, no `isLikelyOpaqueId`. And the three
34
+ * families that do draw it draw it ONLY for values `isLikelyOpaqueId` accepts;
35
+ * that qualifier is the entire content of the behaviour and the sentence never
36
+ * carried it, so a reader was told a human-readable id would degrade visibly
37
+ * when it printed as if it were a name — the defect objectui#8434 was for.
38
+ *
39
+ * It is not replaced by a corrected sentence, because a corrected sentence
40
+ * could not be kept true HERE. `@object-ui/core` does not depend on
41
+ * `@object-ui/fields` in `dependencies`, `peerDependencies` or
42
+ * `devDependencies` — the arrow runs the other way — so no test that can reach
43
+ * this file can render the cell it would describe, and nothing in this repo
44
+ * compares a docblock to rendered output. A replacement would be checked by
45
+ * whatever checked the last one: nobody. Its shelf life was measured too —
46
+ * while objectui#8693 was open, PR #8698 (objectui#8434) sat in the merge
47
+ * queue rewriting the `user` column of that very table: on its head
48
+ * `3692f7218` all three primitives render an unresolved-reference affordance
49
+ * that KEEPS the raw value. A corrected sentence would have been stale before
50
+ * it landed. The other three families are byte-identical on both trees.
51
+ *
52
+ * The storage claim is the half this set actually depends on, and that half IS
53
+ * checkable here (`__tests__/expand-fields.test.ts`, plus the identity pins
54
+ * below). How an unexpanded reference prints belongs next to the renderers in
55
+ * `@object-ui/fields`, where a test can fail.
18
56
  *
19
57
  * Note on `tree`: a self-referencing hierarchy field is a reference too, so it
20
58
  * belongs in this set; whether the backend materialises the expanded object
@@ -31,6 +69,12 @@
31
69
  * the falsification note at the end. Read the list below as the lineage of the
32
70
  * conversions, not as a census; the mechanical fact is the identity pins.
33
71
  *
72
+ * Re-measured for objectui#8693 on `da5e4f69e`: stale again, by FOUR. The
73
+ * commit that corrected the "fourth and last" claim (objectui#5874 / #6064)
74
+ * converged four more private copies of its own and never added them here —
75
+ * the last four bullets below. Re-derive before trusting it; the recipe is at
76
+ * the end of the list.
77
+ *
34
78
  * This is the reference-bearing FAMILY, not the `$expand` builder's private
35
79
  * list, and these concerns read it under several different words:
36
80
  *
@@ -43,7 +87,7 @@
43
87
  * - the grid's bulk-action dialog — `widgetNeedsDataSource` in
44
88
  * `packages/plugin-grid/src/components/bulkParamToField.ts`, which decides
45
89
  * which param widget is handed the grid's `DataSource` and which param field
46
- * shape carries `reference_to` / `display_field`;
90
+ * shape carries `reference` / `displayField`;
47
91
  * - the action-param dialog — `paramToField` in
48
92
  * `packages/app-shell/src/utils/paramToField.ts`, which decides which param
49
93
  * carries a reference target (objectui#5312);
@@ -52,7 +96,33 @@
52
96
  * - the dashboard table's `$expand` whitelist — `computeLookupExpand` in
53
97
  * `packages/plugin-dashboard/src/ObjectDataTable.tsx` (objectui#5692);
54
98
  * - the dashboard's relation/link test — `isLookupType` in
55
- * `packages/plugin-dashboard/src/recordFields.tsx` (objectui#5692).
99
+ * `packages/plugin-dashboard/src/recordFields.tsx` (objectui#5692);
100
+ * - the action-param RESOLVER (one step before `paramToField`) —
101
+ * `packages/app-shell/src/utils/resolveActionParams.ts` (objectui#5874);
102
+ * - the detail header's relation test — `HeaderHighlight.tsx` in
103
+ * `packages/plugin-detail` (objectui#5874);
104
+ * - the detail drawer's relation test — `RecordDetailDrawer.tsx` in
105
+ * `packages/plugin-detail`, reading through `isExpandableFieldType`
106
+ * (objectui#5874);
107
+ * - the kanban board's `$expand` — `ObjectKanban.tsx` in
108
+ * `packages/plugin-kanban`, reading through `buildExpandFields`
109
+ * (objectui#5874).
110
+ *
111
+ * ⚠️ Re-derive rather than trust. One command, from the repo root, over the
112
+ * whole tree with NO pathspec. (Narrowing it to `packages/` + a `*` segment +
113
+ * `/src` matches ZERO files, and would drop `apps/` besides. The two-character
114
+ * sequence is spelled out in words here because writing it literally would end
115
+ * this comment.)
116
+ *
117
+ * git grep -lE 'EXPANDABLE_FIELD_TYPES|isExpandableFieldType|buildExpandFields'
118
+ *
119
+ * 96 files on `da5e4f69e`. Drop tests, changesets and changelogs and the
120
+ * production readers fall into three radii, only the first of which the list
121
+ * above is about: this file plus 8 more naming the set directly, plus 4 more
122
+ * calling `isExpandableFieldType`, plus 13 more calling `buildExpandFields`.
123
+ * The sweep was controlled by planting a reader under `apps/console/src/` and
124
+ * confirming the same command found it — `apps/` holds no reader today, and a
125
+ * zero-hit region is otherwise indistinguishable from a blind spot.
56
126
  *
57
127
  * The third one used to be a second hand-maintained copy, and this comment used
58
128
  * to claim the set "mirrors the form layer's `DATA_SOURCE_FIELD_TYPES`
@@ -8,14 +8,52 @@
8
8
  import { columnIdentity } from './column-identity.js';
9
9
  /**
10
10
  * Relational ("reference-bearing") field types whose stored value is a foreign
11
- * key into another object — and which therefore benefit from `$expand` so a
12
- * list / grid / detail cell can render the related record's display name
13
- * instead of a bare id placeholder ("—").
11
+ * key into another object — and which therefore benefit from `$expand`, so a
12
+ * response carries the related record instead of only its foreign key.
14
13
  *
15
14
  * `user` is a lookup specialised to `sys_user`: it carries the same `reference`
16
15
  * + id storage and the server resolves it through the same expand path as
17
16
  * `lookup` / `master_detail`, so a `user` column that is NOT requested for
18
- * expansion comes back as a raw user id and renders as "—" (objectui#2032).
17
+ * expansion comes back as a raw user id (objectui#2032). What a CELL then
18
+ * prints for that id is deliberately not stated here — see the next section.
19
+ *
20
+ * ## No rendering claim is made here, on purpose — objectui#8693
21
+ *
22
+ * Both paragraphs above used to make one: the `user` sentence ended "…and
23
+ * renders as '—'", and the opening one called the unexpanded cell "a bare id
24
+ * placeholder ('—')". Measured on `da5e4f69e` by resolving each family through
25
+ * `getCellRenderer` and rendering three unexpanded primitives:
26
+ *
27
+ * | unexpanded value | `lookup` / `master_detail` / `tree` | `user` |
28
+ * |----------------------|-------------------------------------|--------------------|
29
+ * | `'u_1'` | `u_1`, plain truncated span | `u_1`, same span |
30
+ * | `'01HQZX9K2M4N6P8R'` | muted `—` | the raw ULID |
31
+ * | `'Ada Lovelace'` | `Ada Lovelace`, plain span | the raw string |
32
+ *
33
+ * Wrong twice over. `user` never drew the em-dash at all — `UserCellRenderer`
34
+ * has no resolver, no `useLookupName`, no `isLikelyOpaqueId`. And the three
35
+ * families that do draw it draw it ONLY for values `isLikelyOpaqueId` accepts;
36
+ * that qualifier is the entire content of the behaviour and the sentence never
37
+ * carried it, so a reader was told a human-readable id would degrade visibly
38
+ * when it printed as if it were a name — the defect objectui#8434 was for.
39
+ *
40
+ * It is not replaced by a corrected sentence, because a corrected sentence
41
+ * could not be kept true HERE. `@object-ui/core` does not depend on
42
+ * `@object-ui/fields` in `dependencies`, `peerDependencies` or
43
+ * `devDependencies` — the arrow runs the other way — so no test that can reach
44
+ * this file can render the cell it would describe, and nothing in this repo
45
+ * compares a docblock to rendered output. A replacement would be checked by
46
+ * whatever checked the last one: nobody. Its shelf life was measured too —
47
+ * while objectui#8693 was open, PR #8698 (objectui#8434) sat in the merge
48
+ * queue rewriting the `user` column of that very table: on its head
49
+ * `3692f7218` all three primitives render an unresolved-reference affordance
50
+ * that KEEPS the raw value. A corrected sentence would have been stale before
51
+ * it landed. The other three families are byte-identical on both trees.
52
+ *
53
+ * The storage claim is the half this set actually depends on, and that half IS
54
+ * checkable here (`__tests__/expand-fields.test.ts`, plus the identity pins
55
+ * below). How an unexpanded reference prints belongs next to the renderers in
56
+ * `@object-ui/fields`, where a test can fail.
19
57
  *
20
58
  * Note on `tree`: a self-referencing hierarchy field is a reference too, so it
21
59
  * belongs in this set; whether the backend materialises the expanded object
@@ -32,6 +70,12 @@ import { columnIdentity } from './column-identity.js';
32
70
  * the falsification note at the end. Read the list below as the lineage of the
33
71
  * conversions, not as a census; the mechanical fact is the identity pins.
34
72
  *
73
+ * Re-measured for objectui#8693 on `da5e4f69e`: stale again, by FOUR. The
74
+ * commit that corrected the "fourth and last" claim (objectui#5874 / #6064)
75
+ * converged four more private copies of its own and never added them here —
76
+ * the last four bullets below. Re-derive before trusting it; the recipe is at
77
+ * the end of the list.
78
+ *
35
79
  * This is the reference-bearing FAMILY, not the `$expand` builder's private
36
80
  * list, and these concerns read it under several different words:
37
81
  *
@@ -44,7 +88,7 @@ import { columnIdentity } from './column-identity.js';
44
88
  * - the grid's bulk-action dialog — `widgetNeedsDataSource` in
45
89
  * `packages/plugin-grid/src/components/bulkParamToField.ts`, which decides
46
90
  * which param widget is handed the grid's `DataSource` and which param field
47
- * shape carries `reference_to` / `display_field`;
91
+ * shape carries `reference` / `displayField`;
48
92
  * - the action-param dialog — `paramToField` in
49
93
  * `packages/app-shell/src/utils/paramToField.ts`, which decides which param
50
94
  * carries a reference target (objectui#5312);
@@ -53,7 +97,33 @@ import { columnIdentity } from './column-identity.js';
53
97
  * - the dashboard table's `$expand` whitelist — `computeLookupExpand` in
54
98
  * `packages/plugin-dashboard/src/ObjectDataTable.tsx` (objectui#5692);
55
99
  * - the dashboard's relation/link test — `isLookupType` in
56
- * `packages/plugin-dashboard/src/recordFields.tsx` (objectui#5692).
100
+ * `packages/plugin-dashboard/src/recordFields.tsx` (objectui#5692);
101
+ * - the action-param RESOLVER (one step before `paramToField`) —
102
+ * `packages/app-shell/src/utils/resolveActionParams.ts` (objectui#5874);
103
+ * - the detail header's relation test — `HeaderHighlight.tsx` in
104
+ * `packages/plugin-detail` (objectui#5874);
105
+ * - the detail drawer's relation test — `RecordDetailDrawer.tsx` in
106
+ * `packages/plugin-detail`, reading through `isExpandableFieldType`
107
+ * (objectui#5874);
108
+ * - the kanban board's `$expand` — `ObjectKanban.tsx` in
109
+ * `packages/plugin-kanban`, reading through `buildExpandFields`
110
+ * (objectui#5874).
111
+ *
112
+ * ⚠️ Re-derive rather than trust. One command, from the repo root, over the
113
+ * whole tree with NO pathspec. (Narrowing it to `packages/` + a `*` segment +
114
+ * `/src` matches ZERO files, and would drop `apps/` besides. The two-character
115
+ * sequence is spelled out in words here because writing it literally would end
116
+ * this comment.)
117
+ *
118
+ * git grep -lE 'EXPANDABLE_FIELD_TYPES|isExpandableFieldType|buildExpandFields'
119
+ *
120
+ * 96 files on `da5e4f69e`. Drop tests, changesets and changelogs and the
121
+ * production readers fall into three radii, only the first of which the list
122
+ * above is about: this file plus 8 more naming the set directly, plus 4 more
123
+ * calling `isExpandableFieldType`, plus 13 more calling `buildExpandFields`.
124
+ * The sweep was controlled by planting a reader under `apps/console/src/` and
125
+ * confirming the same command found it — `apps/` holds no reader today, and a
126
+ * zero-hit region is otherwise indistinguishable from a blind spot.
57
127
  *
58
128
  * The third one used to be a second hand-maintained copy, and this comment used
59
129
  * to claim the set "mirrors the form layer's `DATA_SOURCE_FIELD_TYPES`
@@ -6,11 +6,71 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  /**
9
- * Extract an array of records from various API response formats.
10
- * Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
9
+ * Extract the rows array out of a `DataSource.find()` answer.
11
10
  *
12
- * This utility normalises the different shapes returned by ObjectStack,
13
- * OData, and MSW mock endpoints so that every data-fetching component
14
- * can rely on a single extraction path.
11
+ * Accepted shapes, in the order they are tried:
12
+ *
13
+ * 1. a bare array — the live non-envelope shape fakes and `ValueDataSource`
14
+ * answer with;
15
+ * 2. `{ data: [] }` — the ONE rows member `QueryResult` (`@object-ui/types`)
16
+ * declares;
17
+ * 3. `{ value: [] }` — the OData spelling, still LIVE at this seam (see
18
+ * below).
19
+ *
20
+ * ## `records` is NOT read here (objectui#6839, following #5945 / #6726)
21
+ *
22
+ * A `records` arm used to sit AHEAD of `data` — the precedence inversion
23
+ * objectui#5945 was filed about and objectui#6726 repaired by hand in seven
24
+ * modules. `records` is the below-the-adapter spelling: both
25
+ * `ObjectStackAdapter.normalizeQueryResult` (`@object-ui/data-objectstack`)
26
+ * and `ApiDataSource.normalizeQueryResult` (its `['data','items','results',
27
+ * 'records','value']` envelope loop) CONSUME the server/SDK `records` envelope
28
+ * and return `data` before the answer ever reaches this helper. Every consumer
29
+ * below calls it strictly ABOVE that fold, so the arm was unreachable — and an
30
+ * unreachable tolerant arm is exactly where a non-conforming producer keeps
31
+ * working unrejected (AGENTS.md #0.1). Worse, being FIRST, it outranked the
32
+ * contract's own member: a producer emitting both would have had `data`
33
+ * ignored.
34
+ *
35
+ * Measured on this tree, per consumer rather than once for all of them, since
36
+ * this helper is reached from ten call sites in nine packages:
37
+ *
38
+ * packages/plugin-charts/src/ObjectChart.tsx (x2: rows, ref labels)
39
+ * packages/plugin-dashboard/src/ObjectDataTable.tsx
40
+ * packages/plugin-dashboard/src/ObjectPivotTable.tsx
41
+ * packages/plugin-gantt/src/ObjectGantt.tsx (ref option labels)
42
+ * packages/plugin-kanban/src/ObjectKanban.tsx
43
+ * packages/plugin-timeline/src/ObjectTimeline.tsx
44
+ * packages/core/src/utils/non-grid-row-ceiling.ts (applyNonGridRowCeiling,
45
+ * itself the seam for ObjectCalendar, ObjectGantt, ObjectMap, ObjectTree;
46
+ * measured when it lived in `@object-ui/react`, moved here unchanged by
47
+ * objectui#7508)
48
+ *
49
+ * ZERO of them can be reached by a `records` envelope: no `find()` in any of
50
+ * those packages, nor in the apps and examples that mount them, emits one.
51
+ * CONTROL, so the zero is a reading rather than a miss — the same sweep DOES
52
+ * find `records` envelopes elsewhere: `ViewDataProvider`'s own `ResolvedData`
53
+ * (a different contract, served by that module's own private reader), the raw
54
+ * Cloud HTTP payloads, the client-SDK doubles below `normalizeQueryResult`,
55
+ * the record-visibility batch route stubs, and one live `find()` double at
56
+ * `plugin-list`'s ObjectGallery — a consumer with its OWN unwrap ladder, which
57
+ * does not come through here.
58
+ *
59
+ * ⛔ Do not restore the arm, and ⛔ do not widen `QueryResult` to bless
60
+ * `records` instead — that is a published-type change and the maintainer's
61
+ * call, the same floor objectui#6726 and #6840 respected. A producer that
62
+ * really does speak `records` belongs behind an adapter that folds it into
63
+ * `data`, which is what both adapters above already do.
64
+ *
65
+ * ## `value` STAYS — it is live here, and that is seam-local
66
+ *
67
+ * objectui#6840 / PR #6916 deleted the `value` arm from `ObjectView`'s ladder
68
+ * on a measured zero at THAT seam, and said in as many words that its zero
69
+ * must not be carried here. It does not: five `find()` doubles emit
70
+ * `{ value: [...] }` into this helper today (three in `plugin-kanban`, two in
71
+ * `plugin-calendar`). Deleting it here would break them. Whether `value`
72
+ * should survive at this seam is its own card with its own measurement.
73
+ *
74
+ * Pinned per module by the `*.contractEnvelope-6839.*` suites.
15
75
  */
16
76
  export declare function extractRecords(results: unknown): any[];
@@ -6,21 +6,78 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  /**
9
- * Extract an array of records from various API response formats.
10
- * Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
9
+ * Extract the rows array out of a `DataSource.find()` answer.
11
10
  *
12
- * This utility normalises the different shapes returned by ObjectStack,
13
- * OData, and MSW mock endpoints so that every data-fetching component
14
- * can rely on a single extraction path.
11
+ * Accepted shapes, in the order they are tried:
12
+ *
13
+ * 1. a bare array — the live non-envelope shape fakes and `ValueDataSource`
14
+ * answer with;
15
+ * 2. `{ data: [] }` — the ONE rows member `QueryResult` (`@object-ui/types`)
16
+ * declares;
17
+ * 3. `{ value: [] }` — the OData spelling, still LIVE at this seam (see
18
+ * below).
19
+ *
20
+ * ## `records` is NOT read here (objectui#6839, following #5945 / #6726)
21
+ *
22
+ * A `records` arm used to sit AHEAD of `data` — the precedence inversion
23
+ * objectui#5945 was filed about and objectui#6726 repaired by hand in seven
24
+ * modules. `records` is the below-the-adapter spelling: both
25
+ * `ObjectStackAdapter.normalizeQueryResult` (`@object-ui/data-objectstack`)
26
+ * and `ApiDataSource.normalizeQueryResult` (its `['data','items','results',
27
+ * 'records','value']` envelope loop) CONSUME the server/SDK `records` envelope
28
+ * and return `data` before the answer ever reaches this helper. Every consumer
29
+ * below calls it strictly ABOVE that fold, so the arm was unreachable — and an
30
+ * unreachable tolerant arm is exactly where a non-conforming producer keeps
31
+ * working unrejected (AGENTS.md #0.1). Worse, being FIRST, it outranked the
32
+ * contract's own member: a producer emitting both would have had `data`
33
+ * ignored.
34
+ *
35
+ * Measured on this tree, per consumer rather than once for all of them, since
36
+ * this helper is reached from ten call sites in nine packages:
37
+ *
38
+ * packages/plugin-charts/src/ObjectChart.tsx (x2: rows, ref labels)
39
+ * packages/plugin-dashboard/src/ObjectDataTable.tsx
40
+ * packages/plugin-dashboard/src/ObjectPivotTable.tsx
41
+ * packages/plugin-gantt/src/ObjectGantt.tsx (ref option labels)
42
+ * packages/plugin-kanban/src/ObjectKanban.tsx
43
+ * packages/plugin-timeline/src/ObjectTimeline.tsx
44
+ * packages/core/src/utils/non-grid-row-ceiling.ts (applyNonGridRowCeiling,
45
+ * itself the seam for ObjectCalendar, ObjectGantt, ObjectMap, ObjectTree;
46
+ * measured when it lived in `@object-ui/react`, moved here unchanged by
47
+ * objectui#7508)
48
+ *
49
+ * ZERO of them can be reached by a `records` envelope: no `find()` in any of
50
+ * those packages, nor in the apps and examples that mount them, emits one.
51
+ * CONTROL, so the zero is a reading rather than a miss — the same sweep DOES
52
+ * find `records` envelopes elsewhere: `ViewDataProvider`'s own `ResolvedData`
53
+ * (a different contract, served by that module's own private reader), the raw
54
+ * Cloud HTTP payloads, the client-SDK doubles below `normalizeQueryResult`,
55
+ * the record-visibility batch route stubs, and one live `find()` double at
56
+ * `plugin-list`'s ObjectGallery — a consumer with its OWN unwrap ladder, which
57
+ * does not come through here.
58
+ *
59
+ * ⛔ Do not restore the arm, and ⛔ do not widen `QueryResult` to bless
60
+ * `records` instead — that is a published-type change and the maintainer's
61
+ * call, the same floor objectui#6726 and #6840 respected. A producer that
62
+ * really does speak `records` belongs behind an adapter that folds it into
63
+ * `data`, which is what both adapters above already do.
64
+ *
65
+ * ## `value` STAYS — it is live here, and that is seam-local
66
+ *
67
+ * objectui#6840 / PR #6916 deleted the `value` arm from `ObjectView`'s ladder
68
+ * on a measured zero at THAT seam, and said in as many words that its zero
69
+ * must not be carried here. It does not: five `find()` doubles emit
70
+ * `{ value: [...] }` into this helper today (three in `plugin-kanban`, two in
71
+ * `plugin-calendar`). Deleting it here would break them. Whether `value`
72
+ * should survive at this seam is its own card with its own measurement.
73
+ *
74
+ * Pinned per module by the `*.contractEnvelope-6839.*` suites.
15
75
  */
16
76
  export function extractRecords(results) {
17
77
  if (Array.isArray(results)) {
18
78
  return results;
19
79
  }
20
80
  if (results && typeof results === 'object') {
21
- if (Array.isArray(results.records)) {
22
- return results.records;
23
- }
24
81
  if (Array.isArray(results.data)) {
25
82
  return results.data;
26
83
  }