@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
@@ -5,6 +5,34 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
+ /**
9
+ * Is the predicate TEXT blank — in EITHER spelling? (objectui#3960)
10
+ *
11
+ * `' '` and `{ dialect: 'cel', source: ' ' }` are the same author mistake
12
+ * written two ways, and {@link toPredicateInput} folds neither: it wraps a
13
+ * whitespace-only string into `'${ }'`, and it only folds an envelope whose
14
+ * `source` is exactly `''`. So blankness cannot be derived from the normalizer's
15
+ * answer and has to be stated once, here, for both spellings — trimming one and
16
+ * not the other is the asymmetry objectui#3960 measured (`disabled` greyed out,
17
+ * `ActionRunner` refused to execute, for a predicate that says nothing).
18
+ *
19
+ * This is not a fourth dialect of "empty": it is the SAME rule core's evaluation
20
+ * entries already apply on the value side — `evaluateCondition` on both of its
21
+ * routes (which ask THIS function: the CEL route since objectui#8069, the
22
+ * legacy path since objectui#11262), `evalRowPredicate` (`listConditional.ts`,
23
+ * whose blank reaches `evalFieldPredicate`, which asks it too) — brought to the
24
+ * one place that answers "is there a condition at all?", so the two halves
25
+ * cannot disagree about the same blank.
26
+ *
27
+ * Exported since objectui#8069 for its second consumer, `evalFieldPredicate`
28
+ * (`evaluator/fieldRules.ts`), which is the entry this docblock's list did NOT
29
+ * name because it did not apply the rule: it trimmed the STRING spelling only,
30
+ * so `{ dialect: 'cel', source: ' ' }` went to the engine and came back as a
31
+ * parse fault while `' '` returned the caller's fallback in silence. Reusing
32
+ * this definition rather than writing a fourth `trim()` is the whole point of
33
+ * the paragraph above.
34
+ */
35
+ export declare function isBlankPredicateText(value: unknown): boolean;
8
36
  /**
9
37
  * Is a predicate gate DECLARED on this value — i.e. after normalization, is
10
38
  * there still a CONDITION for {@link ExpressionEvaluator.evaluateCondition} to
@@ -38,18 +66,55 @@
38
66
  * Neither can be read off the normalizer's answer, so both are named here —
39
67
  * see {@link isBlankPredicateText} for why this is the layer that says it.
40
68
  * Core's other predicate entries already treat both as blank:
41
- * `evaluateCondition` (`if (!trimmed) return true`), `evaluateCelCondition`
42
- * (`if (!source.trim()) return true`) and `evalRowPredicate`
43
- * (`evaluator/listConditional.ts`, `if (!source.trim())`).
69
+ * `evaluateCondition` (`isBlankPredicateText`, then `true` — diagnosed on
70
+ * the CEL route since objectui#8069 and on the legacy path since
71
+ * objectui#11262) and `evalRowPredicate` (`evaluator/listConditional.ts`,
72
+ * the caller's fallback, through `evalFieldPredicate`'s `[blank]` report
73
+ * since objectui#11262).
44
74
  * - `{ dialect, source: '' }` — the empty ENVELOPE. This is not an exotic
45
75
  * spelling: `@objectstack/spec`'s `ExpressionInputSchema` normalizes every
46
76
  * authored predicate into an envelope, so "author left the predicate empty"
47
77
  * compiles to exactly this, which makes it the likeliest empty shape in real
48
78
  * metadata (objectui#3850).
49
- * - anything that is not a predicate at all (`0`, `{}`, an array): a value the
50
- * evaluator cannot read must not be the reason a control is disabled or an
51
- * action refuses to run. Fail-open on junk, which is the posture
52
- * `ActionRunner` already committed to (`catch { isDisabled = false }`).
79
+ *
80
+ * The three BLANK shapes above (`''`, whitespace-only text, and an envelope
81
+ * whose `source` is blank) are still folded to "not declared" — that is the
82
+ * verdict — but no longer in silence: each is reported once (objectui#8069,
83
+ * ADR-0137 D4), see {@link reportBlankGate}. `null` / `undefined` (no key) is
84
+ * not blank TEXT and stays silent: nothing was declared there to report.
85
+ *
86
+ * ## What is DECLARED although nothing in it can be evaluated (objectui#11358)
87
+ *
88
+ * A value that is present but is not a predicate the client can evaluate — an
89
+ * envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }` an
90
+ * unmirrored authoring door admits), a number (`0`), an object with no `source`
91
+ * (`{}`), an array — IS a declared gate. Triage's ruling on objectui#11358:
92
+ * "declared and faulting", "reported once per predicate", and the action
93
+ * "hides". It used to be the fifth "nothing to evaluate" shape here, answered
94
+ * "not declared" in silence, so a bulk action gated that way ran over every
95
+ * selected record and the toolbars and row menu showed it to everyone.
96
+ *
97
+ * This function's CODE did not change for it: the normalizer did.
98
+ * {@link toPredicateInput} no longer folds such a value into `undefined`; it
99
+ * returns a `cel` envelope with no `source`, so `toPredicateInput(x) !==
100
+ * undefined` — the line below — answers "declared", and the derivation this
101
+ * definition rests on still holds with no second list. The state is told apart
102
+ * in one place, `isUnevaluablePredicate` (`./unevaluablePredicate.ts`).
103
+ *
104
+ * What a reader then DOES with it is the evaluation step's, not this
105
+ * function's: `ExpressionEvaluator.evaluateCondition` and `evalFieldPredicate`
106
+ * treat it as a FAULT, so each reader answers with the fail direction its key
107
+ * already has for a predicate that cannot be evaluated, and reports it there,
108
+ * once — through `evalFieldPredicate`'s report with the `[unevaluable]` reason,
109
+ * or through the caller's own fault report. That is why this function reports
110
+ * nothing for it: a declared gate always goes on to an evaluation, and two
111
+ * reports for one fault is the noise objectstack-ai/objectstack#5149 removed.
112
+ * On every action `visible` leg that direction is CLOSED (objectui#11212), on
113
+ * every `disabled` leg too (objectui#11242), and the hide-polarity `hidden` /
114
+ * `hiddenOn` legs of `SchemaRenderer` answer "hidden". The keys whose fault
115
+ * direction is OPEN keep it — a `condition` still lets the action run, a
116
+ * legacy `enabled` leaves the control enabled, an action param's `visible`
117
+ * still shows the param — now diagnosed rather than silent.
53
118
  *
54
119
  * A declared-and-`false` gate is DECLARED (`toPredicateInput` returns the
55
120
  * boolean unchanged): `disabled: false` / `visible: false` are verdicts, and
@@ -113,7 +178,26 @@
113
178
  * an empty predicate made the node VANISH (objectui#3955), and
114
179
  * `ActionEngine.getActionsForLocation`'s `visible` filter, which folded some
115
180
  * empty spellings by hand and coerced the rest with `Boolean(raw)`, so
116
- * `visible: 0` hid an action the renderers showed (objectui#3957). Nothing in the
117
- * repo now asks "is a gate declared?" anywhere but here.
181
+ * `visible: 0` hid an action the renderers showed (objectui#3957).
182
+ *
183
+ * The last one joined with objectui#11322: plugin-grid's `hasVisibilityGate`,
184
+ * on the grid's selection bar, asks it through the renderer-side name
185
+ * `hasDeclaredVisibilityGate`. It decides whether a bulk button whose `visible`
186
+ * admits no record is hidden, and `partitionBulkRows` asks it before handing a
187
+ * bulk def's `visible` to `partitionRowsByPredicate`. It had kept the old
188
+ * `!= null && !== ''` scope after every other member of the family moved here,
189
+ * so a whitespace-only `visible` counted as declared on that bar, failed closed
190
+ * for every record, and hid an action the row menu and toolbars showed.
191
+ *
192
+ * Nothing in the repo now asks "is a gate declared?" anywhere but here for
193
+ * the keys this definition serves. What stays outside is a different family,
194
+ * not a twin: the field-rule `*When` keys (ADR-0137) decide it by rules of
195
+ * their own. Examples are the built-in Edit / Delete row item's
196
+ * `visibleWhen != null` (`isBuiltinRowActionVisible`), and the opening test of
197
+ * the shared partitioner `partitionRowsByPredicate` (`null` / `undefined` /
198
+ * `''`), which is the answer its built-in Delete `visibleWhen` callers get. The
199
+ * action family asks here before it calls that partitioner.
200
+ * ⚠️ Nothing re-derives this paragraph: it was read off a source search when
201
+ * objectui#11322 landed, and no test or gate repeats that search.
118
202
  */
119
203
  export declare function hasDeclaredPredicate(value: unknown): boolean;
@@ -6,6 +6,10 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  import { toPredicateInput } from './predicateInput.js';
9
+ // A cycle with `fieldRules.ts`, which imports `isBlankPredicateText` from here.
10
+ // Benign by construction: each side reads the other only inside a function
11
+ // body, never while its module is being evaluated.
12
+ import { evalFieldPredicate } from './fieldRules.js';
9
13
  /**
10
14
  * Is the predicate TEXT blank — in EITHER spelling? (objectui#3960)
11
15
  *
@@ -18,13 +22,22 @@ import { toPredicateInput } from './predicateInput.js';
18
22
  * `ActionRunner` refused to execute, for a predicate that says nothing).
19
23
  *
20
24
  * This is not a fourth dialect of "empty": it is the SAME rule core's evaluation
21
- * entries already apply on the value side — `evaluateCondition`
22
- * (`if (!trimmed) return true`), `evaluateCelCondition` (`if (!source.trim())
23
- * return true`), `evalRowPredicate` (`listConditional.ts`) — brought to the one
24
- * place that answers "is there a condition at all?", so the two halves cannot
25
- * disagree about the same blank.
25
+ * entries already apply on the value side — `evaluateCondition` on both of its
26
+ * routes (which ask THIS function: the CEL route since objectui#8069, the
27
+ * legacy path since objectui#11262), `evalRowPredicate` (`listConditional.ts`,
28
+ * whose blank reaches `evalFieldPredicate`, which asks it too) — brought to the
29
+ * one place that answers "is there a condition at all?", so the two halves
30
+ * cannot disagree about the same blank.
31
+ *
32
+ * Exported since objectui#8069 for its second consumer, `evalFieldPredicate`
33
+ * (`evaluator/fieldRules.ts`), which is the entry this docblock's list did NOT
34
+ * name because it did not apply the rule: it trimmed the STRING spelling only,
35
+ * so `{ dialect: 'cel', source: ' ' }` went to the engine and came back as a
36
+ * parse fault while `' '` returned the caller's fallback in silence. Reusing
37
+ * this definition rather than writing a fourth `trim()` is the whole point of
38
+ * the paragraph above.
26
39
  */
27
- function isBlankPredicateText(value) {
40
+ export function isBlankPredicateText(value) {
28
41
  if (typeof value === 'string')
29
42
  return value.trim() === '';
30
43
  if (value !== null && typeof value === 'object') {
@@ -67,18 +80,55 @@ function isBlankPredicateText(value) {
67
80
  * Neither can be read off the normalizer's answer, so both are named here —
68
81
  * see {@link isBlankPredicateText} for why this is the layer that says it.
69
82
  * Core's other predicate entries already treat both as blank:
70
- * `evaluateCondition` (`if (!trimmed) return true`), `evaluateCelCondition`
71
- * (`if (!source.trim()) return true`) and `evalRowPredicate`
72
- * (`evaluator/listConditional.ts`, `if (!source.trim())`).
83
+ * `evaluateCondition` (`isBlankPredicateText`, then `true` — diagnosed on
84
+ * the CEL route since objectui#8069 and on the legacy path since
85
+ * objectui#11262) and `evalRowPredicate` (`evaluator/listConditional.ts`,
86
+ * the caller's fallback, through `evalFieldPredicate`'s `[blank]` report
87
+ * since objectui#11262).
73
88
  * - `{ dialect, source: '' }` — the empty ENVELOPE. This is not an exotic
74
89
  * spelling: `@objectstack/spec`'s `ExpressionInputSchema` normalizes every
75
90
  * authored predicate into an envelope, so "author left the predicate empty"
76
91
  * compiles to exactly this, which makes it the likeliest empty shape in real
77
92
  * metadata (objectui#3850).
78
- * - anything that is not a predicate at all (`0`, `{}`, an array): a value the
79
- * evaluator cannot read must not be the reason a control is disabled or an
80
- * action refuses to run. Fail-open on junk, which is the posture
81
- * `ActionRunner` already committed to (`catch { isDisabled = false }`).
93
+ *
94
+ * The three BLANK shapes above (`''`, whitespace-only text, and an envelope
95
+ * whose `source` is blank) are still folded to "not declared" — that is the
96
+ * verdict — but no longer in silence: each is reported once (objectui#8069,
97
+ * ADR-0137 D4), see {@link reportBlankGate}. `null` / `undefined` (no key) is
98
+ * not blank TEXT and stays silent: nothing was declared there to report.
99
+ *
100
+ * ## What is DECLARED although nothing in it can be evaluated (objectui#11358)
101
+ *
102
+ * A value that is present but is not a predicate the client can evaluate — an
103
+ * envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }` an
104
+ * unmirrored authoring door admits), a number (`0`), an object with no `source`
105
+ * (`{}`), an array — IS a declared gate. Triage's ruling on objectui#11358:
106
+ * "declared and faulting", "reported once per predicate", and the action
107
+ * "hides". It used to be the fifth "nothing to evaluate" shape here, answered
108
+ * "not declared" in silence, so a bulk action gated that way ran over every
109
+ * selected record and the toolbars and row menu showed it to everyone.
110
+ *
111
+ * This function's CODE did not change for it: the normalizer did.
112
+ * {@link toPredicateInput} no longer folds such a value into `undefined`; it
113
+ * returns a `cel` envelope with no `source`, so `toPredicateInput(x) !==
114
+ * undefined` — the line below — answers "declared", and the derivation this
115
+ * definition rests on still holds with no second list. The state is told apart
116
+ * in one place, `isUnevaluablePredicate` (`./unevaluablePredicate.ts`).
117
+ *
118
+ * What a reader then DOES with it is the evaluation step's, not this
119
+ * function's: `ExpressionEvaluator.evaluateCondition` and `evalFieldPredicate`
120
+ * treat it as a FAULT, so each reader answers with the fail direction its key
121
+ * already has for a predicate that cannot be evaluated, and reports it there,
122
+ * once — through `evalFieldPredicate`'s report with the `[unevaluable]` reason,
123
+ * or through the caller's own fault report. That is why this function reports
124
+ * nothing for it: a declared gate always goes on to an evaluation, and two
125
+ * reports for one fault is the noise objectstack-ai/objectstack#5149 removed.
126
+ * On every action `visible` leg that direction is CLOSED (objectui#11212), on
127
+ * every `disabled` leg too (objectui#11242), and the hide-polarity `hidden` /
128
+ * `hiddenOn` legs of `SchemaRenderer` answer "hidden". The keys whose fault
129
+ * direction is OPEN keep it — a `condition` still lets the action run, a
130
+ * legacy `enabled` leaves the control enabled, an action param's `visible`
131
+ * still shows the param — now diagnosed rather than silent.
82
132
  *
83
133
  * A declared-and-`false` gate is DECLARED (`toPredicateInput` returns the
84
134
  * boolean unchanged): `disabled: false` / `visible: false` are verdicts, and
@@ -142,11 +192,62 @@ function isBlankPredicateText(value) {
142
192
  * an empty predicate made the node VANISH (objectui#3955), and
143
193
  * `ActionEngine.getActionsForLocation`'s `visible` filter, which folded some
144
194
  * empty spellings by hand and coerced the rest with `Boolean(raw)`, so
145
- * `visible: 0` hid an action the renderers showed (objectui#3957). Nothing in the
146
- * repo now asks "is a gate declared?" anywhere but here.
195
+ * `visible: 0` hid an action the renderers showed (objectui#3957).
196
+ *
197
+ * The last one joined with objectui#11322: plugin-grid's `hasVisibilityGate`,
198
+ * on the grid's selection bar, asks it through the renderer-side name
199
+ * `hasDeclaredVisibilityGate`. It decides whether a bulk button whose `visible`
200
+ * admits no record is hidden, and `partitionBulkRows` asks it before handing a
201
+ * bulk def's `visible` to `partitionRowsByPredicate`. It had kept the old
202
+ * `!= null && !== ''` scope after every other member of the family moved here,
203
+ * so a whitespace-only `visible` counted as declared on that bar, failed closed
204
+ * for every record, and hid an action the row menu and toolbars showed.
205
+ *
206
+ * Nothing in the repo now asks "is a gate declared?" anywhere but here for
207
+ * the keys this definition serves. What stays outside is a different family,
208
+ * not a twin: the field-rule `*When` keys (ADR-0137) decide it by rules of
209
+ * their own. Examples are the built-in Edit / Delete row item's
210
+ * `visibleWhen != null` (`isBuiltinRowActionVisible`), and the opening test of
211
+ * the shared partitioner `partitionRowsByPredicate` (`null` / `undefined` /
212
+ * `''`), which is the answer its built-in Delete `visibleWhen` callers get. The
213
+ * action family asks here before it calls that partitioner.
214
+ * ⚠️ Nothing re-derives this paragraph: it was read off a source search when
215
+ * objectui#11322 landed, and no test or gate repeats that search.
147
216
  */
148
217
  export function hasDeclaredPredicate(value) {
149
- if (isBlankPredicateText(value))
218
+ if (isBlankPredicateText(value)) {
219
+ reportBlankGate(value);
150
220
  return false;
221
+ }
151
222
  return toPredicateInput(value) !== undefined;
152
223
  }
224
+ /**
225
+ * Say out loud that a DECLARED gate was blank — the one side effect
226
+ * {@link hasDeclaredPredicate} has (objectui#8069).
227
+ *
228
+ * The fold above is objectui#3850 / #3960's ruled verdict and it stands: a
229
+ * blank gate is "no gate". What ADR-0137 D4 removed is the SILENCE of it — "a
230
+ * gate predicate that is blank or faulting is diagnosed, never a silent
231
+ * `true`" — and this fold is where a blank `visible` / `hidden` / `enabled` /
232
+ * `disabled` / `condition` stopped before it could reach any evaluator that
233
+ * might have said so.
234
+ *
235
+ * Reported through `evalFieldPredicate`'s own `[blank]` report — the channel
236
+ * every other predicate fault already uses, with its once-per-(text, locator)
237
+ * dedupe — and not through a warner of its own. It makes no engine call: a
238
+ * blank predicate never reaches the engine. The `false` it is handed is this
239
+ * function's answer ("not declared"); nothing reads it back.
240
+ *
241
+ * ⚠️ The locator is FIXED, because this question is key-neutral and receives
242
+ * nothing that names the node. So the dedupe is per blank SPELLING across the
243
+ * app: `''`, `' '` and a blank envelope each warn once, and a second node
244
+ * with the same blank spelling does not warn again. That is weaker than the
245
+ * field-rule path, which joins the field's locator to a blank's key, and it is
246
+ * the price of diagnosing at the one definition rather than at each of its
247
+ * callers.
248
+ */
249
+ function reportBlankGate(value) {
250
+ evalFieldPredicate(value, {}, false, undefined, undefined, {
251
+ context: 'a declared gate, read as no gate',
252
+ });
253
+ }
@@ -44,12 +44,54 @@ export interface FieldPredicateDiagnostic {
44
44
  /**
45
45
  * Evaluate a field-rule CEL predicate against a record.
46
46
  *
47
+ * ## `fallback` is a per-CALL-SITE policy, not a default this helper owns
48
+ *
49
+ * The parameter is required and carries no default value, so every call site
50
+ * states a direction of its own — and the directions in this repo do not
51
+ * agree. Five distinct fault policies share this one helper today, which is
52
+ * readable nowhere but here (objectui#8069):
53
+ *
54
+ * 1. **Fixed permissive, paired with an equal "no rule" literal.**
55
+ * {@link resolveFieldRuleState} (`true` / `false` / `false`) and
56
+ * `resolveVisibleOptions` (`evaluator/optionRules.ts`, `true`). A fault
57
+ * produces the permissive verdict, indistinguishable from the verdict for
58
+ * "the author wrote no rule" — see the `…_FAULTED` / `…_ABSENT` note below.
59
+ * 2. **Fixed permissive, unguarded — ONE literal answers absent *and*
60
+ * faulted.** Every visibility call site outside core: the form renderer
61
+ * (`components/renderers/form/form.tsx`), console's `FormPage`, app-shell's
62
+ * `ScreenView`, `plugin-form`'s `WizardForm`. All of them pass `true`, and
63
+ * none has a separate absent branch at all.
64
+ * 3. **Caller-parameterised.** `evalRowPredicate`'s single-eval fast route
65
+ * (`evaluator/listConditional.ts`) forwards `opts.fallback`, so the
66
+ * direction is the mounting surface's to state.
67
+ * 4. **Divergence probe → fault FLAG.** `evalCel`
68
+ * (`evaluator/listConditional.ts`) calls this helper once per direction with
69
+ * `warn: false` + `onFault`: a verdict that tracks the fallback in BOTH runs
70
+ * is a fault, and the caller emits its own labelled warning and returns its
71
+ * own fallback.
72
+ * 5. **Divergence probe → THROW.** `ExpressionEvaluator.evaluateCelCondition`
73
+ * under `throwOnError` runs the same two-call trick and converts the
74
+ * disagreement into `throw new Error('CEL predicate failed to evaluate: …')`.
75
+ *
76
+ * ⚠️ The last two policies exist **because** `fallback` is free to specify: they
77
+ * detect a fault by disagreeing with themselves. Fixing a direction *inside*
78
+ * this helper would remove the mechanism they are built on, which is why
79
+ * ADR-0137 D5 keeps `fallback` free and declares the direction at the
80
+ * field-rule and gate layer instead.
81
+ *
47
82
  * @param pred The `visibleWhen` / `readonlyWhen` / `requiredWhen` predicate.
48
83
  * @param record The live form values (overlays prior persisted record).
49
- * @param fallback Value to return when the predicate is absent or fails to
50
- * evaluate. Pick the *safe* default for the caller:
51
- * `false` for readonly/required (don't lock/block on error),
52
- * `true` for visibility (don't hide on error).
84
+ * @param fallback Value to return when the predicate is ABSENT (`null` /
85
+ * `undefined`), and — separately — the value returned when a
86
+ * present predicate cannot be evaluated. ⚠️ Those are two
87
+ * questions this one parameter answers with one value; a
88
+ * caller that wants different answers must branch before the
89
+ * call, as {@link resolveFieldRuleState} does. The historic
90
+ * advice is to pick the *safe* default (`false` for
91
+ * readonly/required — don't lock/block on error; `true` for
92
+ * visibility — don't hide on error). ADR-0137 kept those
93
+ * directions for RENDER (D3) and put the answer to a broken
94
+ * field rule at SUBMIT (D2) — see {@link FieldRuleFaults}.
53
95
  * @param previous The prior persisted record, if any (for `previous.*` refs).
54
96
  * @param scope Extra top-level scope variables bound alongside `record` —
55
97
  * e.g. `{ parent }` so an inline line-item cell can reference
@@ -61,6 +103,45 @@ export interface FieldPredicateDiagnostic {
61
103
  * is unaffected either way.
62
104
  */
63
105
  export declare function evalFieldPredicate(pred: FieldRulePredicate | undefined | null, record: Record<string, unknown>, fallback: boolean, previous?: Record<string, unknown>, scope?: Record<string, unknown>, diagnostic?: FieldPredicateDiagnostic): boolean;
106
+ /**
107
+ * Which of {@link resolveFieldRuleState}'s rules could NOT be evaluated, and
108
+ * why — keyed by rule, valued by the reason the one-time warning prints after
109
+ * `Reason:` (`"[kind] message"`). A key is present only for a rule that ran and
110
+ * faulted (objectui#8069).
111
+ *
112
+ * This is the report a SUBMIT path reads — ADR-0137 D2, "at submit time, a
113
+ * field-rule predicate that cannot be evaluated refuses the write and names the
114
+ * field and the rule" — so that the refusal is decided by the very evaluation
115
+ * that drew the verdict, never by a second one with its own record assembly.
116
+ * Which rules a submit path refuses on is that path's ruling, not this
117
+ * function's: under objectui#8069's ruling (Q1 = B, one judge per rule) the
118
+ * client refuses `visibleWhen` only, because no server evaluates it, and leaves
119
+ * `readonlyWhen` / `requiredWhen` to the server's own D2 refusal. The other two
120
+ * keys are reported all the same, so the report does not encode that ruling.
121
+ *
122
+ * A BLANK predicate IS in it, as the `[blank]` reason. ADR-0137 makes a
123
+ * declared-but-blank field rule a third state, not a spelling of "no rule":
124
+ * "A blank predicate takes this path too, wherever one is already stored — D1
125
+ * keeps new ones from being authored, and D2 is what a stored one meets" (D2),
126
+ * and its Alternatives refuse "blank means no rule" for this triad by name. So
127
+ * a stored blank `visibleWhen` is refused at submit like any other fault. The
128
+ * trap that would otherwise make — an authorable value that no submit can ever
129
+ * pass — is closed at AUTHORING: the triad's form wire refuses a blank at parse
130
+ * (`FieldRulePredicateWireSchema` in `@object-ui/types`' `form.zod.ts`, D1).
131
+ * The render verdict of a blank rule is unchanged (D3: the permissive one).
132
+ * A blank GATE is a different thing: `hasDeclaredPredicate` still folds it to
133
+ * "no gate" and only diagnoses it (D4) — it never reaches this function.
134
+ *
135
+ * Deliberately NOT in it: a rule the verdict did not need. `readonlyWhen` under
136
+ * a static `readonly: true`, and `requiredWhen` under `serverOwnedValue` or a
137
+ * static `required: true`, are short-circuited and never run, so they cannot
138
+ * fault.
139
+ */
140
+ export interface FieldRuleFaults {
141
+ visibleWhen?: string;
142
+ readonlyWhen?: string;
143
+ requiredWhen?: string;
144
+ }
64
145
  /**
65
146
  * Resolve the effective `{ visible, readonly, required }` state for a field
66
147
  * given its conditional rules and the live record. Each `*When` rule, when
@@ -90,6 +171,8 @@ export declare function evalFieldPredicate(pred: FieldRulePredicate | undefined
90
171
  * `"field 'amount'"`. The warning then reads
91
172
  * `visibleWhen of field 'amount' …`; without it, the rule
92
173
  * kind alone is reported.
174
+ * @returns The three verdicts, plus `faults` — see {@link FieldRuleFaults} for
175
+ * what a submit path reads from it and what it leaves out.
93
176
  */
94
177
  export declare function resolveFieldRuleState(rules: {
95
178
  visibleWhen?: FieldRulePredicate;
@@ -103,4 +186,5 @@ export declare function resolveFieldRuleState(rules: {
103
186
  visible: boolean;
104
187
  readonly: boolean;
105
188
  required: boolean;
189
+ faults: FieldRuleFaults;
106
190
  };