@object-ui/core 17.5.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 (157) hide show
  1. package/CHANGELOG.md +7404 -0
  2. package/README.md +127 -26
  3. package/dist/actions/ActionEngine.d.ts +5 -3
  4. package/dist/actions/ActionEngine.js +14 -10
  5. package/dist/actions/ActionRunner.d.ts +363 -30
  6. package/dist/actions/ActionRunner.js +544 -58
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +75 -13
  9. package/dist/actions/actionKeys.js +131 -16
  10. package/dist/actions/actionResponse.js +3 -3
  11. package/dist/actions/index.d.ts +2 -0
  12. package/dist/actions/index.js +2 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/actions/recordIdParam.d.ts +77 -0
  16. package/dist/actions/recordIdParam.js +37 -0
  17. package/dist/actions/serverActionHandler.d.ts +1 -1
  18. package/dist/actions/serverActionHandler.js +1 -1
  19. package/dist/adapters/ValueDataSource.d.ts +6 -1
  20. package/dist/adapters/ValueDataSource.js +1090 -98
  21. package/dist/adapters/batchTransaction.d.ts +1 -1
  22. package/dist/adapters/batchTransaction.js +1 -1
  23. package/dist/builder/schema-builder.d.ts +38 -81
  24. package/dist/builder/schema-builder.js +33 -152
  25. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  26. package/dist/data-scope/ViewDataProvider.js +21 -1
  27. package/dist/data-scope/element-data-source.d.ts +261 -7
  28. package/dist/data-scope/element-data-source.js +342 -52
  29. package/dist/data-scope/index.d.ts +9 -4
  30. package/dist/data-scope/index.js +9 -4
  31. package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
  32. package/dist/evaluator/ExpressionEvaluator.js +226 -13
  33. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  34. package/dist/evaluator/FormulaFunctions.js +166 -27
  35. package/dist/evaluator/SafeExpressionParser.js +0 -4
  36. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  37. package/dist/evaluator/declaredPredicate.js +118 -17
  38. package/dist/evaluator/fieldRules.d.ts +107 -4
  39. package/dist/evaluator/fieldRules.js +231 -35
  40. package/dist/evaluator/index.d.ts +2 -0
  41. package/dist/evaluator/index.js +2 -0
  42. package/dist/evaluator/listConditional.d.ts +125 -9
  43. package/dist/evaluator/listConditional.js +167 -26
  44. package/dist/evaluator/optionRules.d.ts +126 -0
  45. package/dist/evaluator/optionRules.js +141 -4
  46. package/dist/evaluator/predicateInput.d.ts +15 -4
  47. package/dist/evaluator/predicateInput.js +35 -2
  48. package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
  49. package/dist/evaluator/rowPredicateCanon.js +158 -0
  50. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  51. package/dist/evaluator/subjectPermissions.js +44 -0
  52. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  53. package/dist/evaluator/unevaluablePredicate.js +70 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +97 -2
  56. package/dist/protocols/index.d.ts +0 -1
  57. package/dist/protocols/index.js +9 -1
  58. package/dist/registry/Registry.d.ts +397 -48
  59. package/dist/registry/Registry.js +263 -11
  60. package/dist/registry/WidgetRegistry.js +7 -3
  61. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  62. package/dist/registry/html-tier-intrinsics.js +110 -0
  63. package/dist/registry/public-blocks.d.ts +8 -0
  64. package/dist/registry/public-blocks.js +57 -0
  65. package/dist/theme/ThemeEngine.js +1 -1
  66. package/dist/theme/index.d.ts +1 -1
  67. package/dist/theme/index.js +1 -1
  68. package/dist/types/index.d.ts +92 -13
  69. package/dist/utils/chart-category-key.d.ts +82 -0
  70. package/dist/utils/chart-category-key.js +84 -0
  71. package/dist/utils/chart-measure-key.d.ts +64 -0
  72. package/dist/utils/chart-measure-key.js +65 -0
  73. package/dist/utils/chart-presentation.d.ts +188 -0
  74. package/dist/utils/chart-presentation.js +345 -0
  75. package/dist/utils/chart-series.d.ts +271 -31
  76. package/dist/utils/chart-series.js +478 -55
  77. package/dist/utils/column-identity.d.ts +43 -0
  78. package/dist/utils/column-identity.js +47 -1
  79. package/dist/utils/column-sortability.d.ts +157 -0
  80. package/dist/utils/column-sortability.js +217 -0
  81. package/dist/utils/dashboard-filters.d.ts +38 -6
  82. package/dist/utils/dashboard-filters.js +107 -61
  83. package/dist/utils/dataset-format.d.ts +211 -30
  84. package/dist/utils/dataset-format.js +407 -52
  85. package/dist/utils/date-display.d.ts +354 -0
  86. package/dist/utils/date-display.js +463 -0
  87. package/dist/utils/date-macros.js +7 -0
  88. package/dist/utils/debug.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 +171 -11
  94. package/dist/utils/expand-fields.js +171 -11
  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 +152 -4
  98. package/dist/utils/filter-converter.js +1092 -22
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -34
  101. package/dist/utils/freeze-schema.d.ts +38 -1
  102. package/dist/utils/freeze-schema.js +10 -0
  103. package/dist/utils/grouping-fields.d.ts +61 -0
  104. package/dist/utils/grouping-fields.js +84 -0
  105. package/dist/utils/humanize-label.d.ts +60 -0
  106. package/dist/utils/humanize-label.js +62 -0
  107. package/dist/utils/managedBy.d.ts +30 -5
  108. package/dist/utils/managedBy.js +5 -5
  109. package/dist/utils/native-date-value.d.ts +48 -0
  110. package/dist/utils/native-date-value.js +167 -0
  111. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  112. package/dist/utils/non-grid-row-ceiling.js +141 -0
  113. package/dist/utils/normalize-list-view.d.ts +131 -1
  114. package/dist/utils/normalize-list-view.js +248 -6
  115. package/dist/utils/number-display.d.ts +70 -17
  116. package/dist/utils/number-display.js +34 -12
  117. package/dist/utils/object-aggregate-query.d.ts +113 -0
  118. package/dist/utils/object-aggregate-query.js +50 -0
  119. package/dist/utils/parent-scope.d.ts +123 -0
  120. package/dist/utils/parent-scope.js +140 -0
  121. package/dist/utils/predicate-fields.d.ts +62 -4
  122. package/dist/utils/predicate-fields.js +169 -4
  123. package/dist/utils/record-color.d.ts +75 -0
  124. package/dist/utils/record-color.js +65 -0
  125. package/dist/utils/record-source.d.ts +298 -0
  126. package/dist/utils/record-source.js +331 -0
  127. package/dist/utils/record-title.d.ts +148 -8
  128. package/dist/utils/record-title.js +220 -26
  129. package/dist/utils/reference-keys.d.ts +21 -22
  130. package/dist/utils/reference-keys.js +532 -29
  131. package/dist/utils/retired-field-types.d.ts +90 -0
  132. package/dist/utils/retired-field-types.js +124 -0
  133. package/dist/utils/sort-query.d.ts +128 -26
  134. package/dist/utils/sort-query.js +240 -13
  135. package/dist/utils/text-comparand.d.ts +92 -0
  136. package/dist/utils/text-comparand.js +99 -0
  137. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  138. package/dist/utils/unmaterialized-fields.js +57 -0
  139. package/dist/utils/without-denied-fields.d.ts +49 -0
  140. package/dist/utils/without-denied-fields.js +52 -0
  141. package/dist/validation/index.d.ts +9 -4
  142. package/dist/validation/index.js +9 -4
  143. package/dist/validation/schema-validator.d.ts +1 -1
  144. package/dist/validation/schema-validator.js +305 -82
  145. package/dist/validation/server-owned-value.d.ts +62 -0
  146. package/dist/validation/server-owned-value.js +99 -0
  147. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  148. package/dist/validation/validators/object-validation-engine.js +14 -5
  149. package/package.json +8 -9
  150. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  151. package/dist/__benchmarks__/core.bench.js +0 -53
  152. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  153. package/dist/data-scope/DataScopeManager.js +0 -229
  154. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  155. package/dist/protocols/ResponsiveProtocol.js +0 -158
  156. package/dist/validation/validation-engine.d.ts +0 -88
  157. package/dist/validation/validation-engine.js +0 -434
package/README.md CHANGED
@@ -4,9 +4,10 @@ Core logic, types, and validation for Object UI. Zero React dependencies.
4
4
 
5
5
  ## Features
6
6
 
7
- - 🎯 **Type Definitions** - Complete TypeScript schemas for all components
7
+ - 🎯 **Type Definitions** - Re-exported runtime types; the component schema
8
+ vocabulary itself is `@object-ui/types`
8
9
  - 🔍 **Component Registry** - Framework-agnostic component registration system
9
- - 📊 **Data Scope** - Data scope management and expression evaluation
10
+ - 🧮 **Expressions** - `${...}` expression evaluation against a context
10
11
  - ✅ **Validation** - Zod-based schema validation
11
12
  - 🚀 **Zero React** - Can run in Node.js or any JavaScript environment
12
13
 
@@ -20,18 +21,24 @@ npm install @object-ui/core
20
21
 
21
22
  ### Type Definitions
22
23
 
24
+ The component schema vocabulary lives in `@object-ui/types`. Core depends on
25
+ that package and does not re-export it, so import the types from there. The
26
+ page node type is `PageNodeSchema` — the SDUI node, as distinct from the
27
+ authored page document.
28
+
23
29
  ```typescript
24
- import type {
25
- PageSchema,
26
- FormSchema,
30
+ import type {
31
+ PageNodeSchema,
32
+ FormSchema,
27
33
  InputSchema,
28
- BaseSchema
29
- } from '@object-ui/core'
34
+ BaseSchema
35
+ } from '@object-ui/types'
30
36
 
31
- const mySchema: PageSchema = {
37
+ const mySchema: PageNodeSchema = {
32
38
  type: 'page',
39
+ pageType: 'app',
33
40
  title: 'My Page',
34
- body: []
41
+ children: []
35
42
  }
36
43
  ```
37
44
 
@@ -39,23 +46,31 @@ const mySchema: PageSchema = {
39
46
 
40
47
  ```typescript
41
48
  import { ComponentRegistry } from '@object-ui/core'
49
+ import type { ComponentRenderer } from '@object-ui/core'
50
+
51
+ // Your component, in whatever renderer shape the host framework uses.
52
+ declare const buttonRenderer: ComponentRenderer
42
53
 
43
- const registry = new ComponentRegistry()
44
- registry.register('button', buttonMetadata)
45
- const metadata = registry.get('button')
54
+ ComponentRegistry.register('button', buttonRenderer)
55
+ const renderer = ComponentRegistry.get('button')
46
56
  ```
47
57
 
48
- ### Data Scope
58
+ `ComponentRegistry` is a process-level singleton exported by `@object-ui/core`;
59
+ `SchemaRenderer` resolves every `type` against it, so a component registered
60
+ here is renderable from schema anywhere in the app. `register()`'s second
61
+ argument is the component itself; registration metadata is its optional third
62
+ argument, and `getMeta()` — not `get()` — reads that metadata back.
49
63
 
50
- ```typescript
51
- import { DataScope } from '@object-ui/core'
64
+ ### Expressions
52
65
 
53
- const scope = new DataScope({
54
- user: { name: 'John', role: 'admin' }
55
- })
66
+ `evaluateExpression` evaluates a `${...}` expression against a context.
56
67
 
57
- const userName = scope.get('user.name') // 'John'
58
- const isAdmin = scope.evaluate('${user.role === "admin"}') // true
68
+ ```typescript
69
+ import { evaluateExpression } from '@object-ui/core'
70
+
71
+ const isAdmin = evaluateExpression('${user.role === "admin"}', {
72
+ user: { name: 'John', role: 'admin' },
73
+ }) // true
59
74
  ```
60
75
 
61
76
  ### Server Action Dispatch (`createServerActionHandler`)
@@ -68,6 +83,14 @@ object scope, which are injected:
68
83
 
69
84
  ```typescript
70
85
  import { createServerActionHandler } from '@object-ui/core'
86
+ import type { ActionRunner, ServerActionFetch } from '@object-ui/core'
87
+
88
+ // Injected by the host. Each stand-in is typed from the shipped surface, so
89
+ // this example is checked against the factory's own config rather than a copy.
90
+ declare const myAuthenticatedFetch: ServerActionFetch
91
+ declare const currentObject: string | undefined
92
+ declare const refetchData: () => void
93
+ declare const runner: ActionRunner
71
94
 
72
95
  const script = createServerActionHandler({
73
96
  fetch: myAuthenticatedFetch, // your auth wrapper (Bearer/cookies/...)
@@ -87,21 +110,22 @@ action identity (ADR-0110), the record-id resolution dance (`_rowRecord`,
87
110
  guard, and the `/actions` response-envelope rule (`interpretActionResponse` /
88
111
  `readActionPayload`, also exported).
89
112
 
90
- ### System Views (`defineView`)
113
+ ### System Views (`defineSystemView`)
91
114
 
92
115
  Schemas authored in source code are part of the product contract and must
93
- not be mutated at runtime. Wrap them with `defineView()` to deep-freeze the
94
- graph and tag it as a *System View*.
116
+ not be mutated at runtime. Wrap them with `defineSystemView()` to deep-freeze
117
+ the graph and tag it as a *System View*.
95
118
 
96
119
  ```typescript
97
- import { defineView, cloneAsOverride, isSystemView } from '@object-ui/core'
120
+ import { defineSystemView, cloneAsOverride, isSystemView } from '@object-ui/core'
98
121
 
99
- export const userListView = defineView({
122
+ export const userListView = defineSystemView({
100
123
  type: 'list',
101
124
  data: { object: 'User' },
102
125
  columns: [{ name: 'email' }],
103
126
  })
104
127
 
128
+ // @ts-expect-error readonly by defineSystemView — refused at compile time, not just at run time
105
129
  userListView.columns.push({ name: 'name' }) // ❌ TypeError (strict mode)
106
130
  isSystemView(userListView) // ✅ true
107
131
 
@@ -115,13 +139,90 @@ isSystemView(draft) // false — clone is no longer Sys
115
139
 
116
140
  | Tier | Source | Mutable? | API |
117
141
  | ----------- | --------------------- | -------- | --------------------------- |
118
- | System View | code (`import` / `as const`) | ❌ frozen | `defineView()` |
142
+ | System View | code (`import` / `as const`) | ❌ frozen | `defineSystemView()` |
119
143
  | Tenant View | backend / DB | ⚠️ admin only | `cloneAsOverride()` + persist |
120
144
  | User View | localStorage / API | ✅ user-editable | `cloneAsOverride()` + persist |
121
145
 
122
146
  `Date`, `RegExp`, `Map`, `Set`, and class instances passed via `props` are
123
147
  intentionally **not** frozen so infrastructure objects keep working.
124
148
 
149
+ ### Field-level read gate (`withoutDeniedFields`)
150
+
151
+ Field-level security gates what the renderer DRAWS (objectui#7215 /
152
+ objectui#7230): a value the loaded permission policy denies is not shown, even
153
+ when a backend serves it. `withoutDeniedFields` is that rule for one record —
154
+ call it on a row before building a display value from the whole row (a record
155
+ title, a lookup label, a search-hit label), so a denied field reads exactly as
156
+ an absent one.
157
+
158
+ ```typescript
159
+ import { withoutDeniedFields } from '@object-ui/core'
160
+
161
+ // `policy` is structural: `usePermissions()` from `@object-ui/permissions`
162
+ // satisfies it, as does any `{ isLoaded, checkField }` object.
163
+ const policy = {
164
+ isLoaded: true,
165
+ checkField: (object: string, field: string) => !(object === 'account' && field === 'salary'),
166
+ }
167
+
168
+ withoutDeniedFields({ id: 'a1', name: 'Acme', salary: 100 }, policy, 'account')
169
+ // { id: 'a1', name: 'Acme' }
170
+
171
+ // A declared id field is kept without asking the policy, like `id` and `_id`:
172
+ withoutDeniedFields({ account_code: 'A-1', salary: 100 }, policy, 'account', ['account_code'])
173
+ // { account_code: 'A-1' }
174
+ ```
175
+
176
+ - `id`, `_id` and every key in the optional fourth argument are never judged.
177
+ - Before the policy loads, with no policy, with no object name, or for a value
178
+ that is not an object, the record comes back as it is.
179
+ - When nothing is withheld the SAME object comes back, so callers can tell the
180
+ two cases apart by identity.
181
+
182
+ ### Undo snapshot for an update (`captureUpdateUndoData`)
183
+
184
+ An `undoable` update restores exactly the fields it wrote, from their values
185
+ before the write. `captureUpdateUndoData` is the one rule for reading those
186
+ values off a record. `ActionRunner` uses it, and so should any surface that
187
+ builds its own `update` Undo operation.
188
+
189
+ ```typescript
190
+ import { captureUpdateUndoData, type FieldContainerLike } from '@object-ui/core'
191
+
192
+ // The written object's field definitions, as its schema serves them
193
+ // (`objectSchema.fields`). Below, `status` is a text field, `account` a
194
+ // lookup and `config` a json field.
195
+ declare const fields: FieldContainerLike
196
+
197
+ captureUpdateUndoData(['status'], { id: 't1', status: 'open' }, fields) // { status: 'open' }
198
+ captureUpdateUndoData(['status'], { id: 't1', status: null }, fields) // { status: null }
199
+ captureUpdateUndoData(['status'], { id: 't1' }, fields) // undefined
200
+
201
+ // A relation read with `$expand` is captured as its stored id.
202
+ captureUpdateUndoData(['account'], { id: 't1', account: { id: 'a1', name: 'Acme' } }, fields)
203
+ // { account: 'a1' }
204
+
205
+ // Any other field is captured as the record carries it, whatever its shape.
206
+ captureUpdateUndoData(['config'], { id: 't1', config: { id: 'c1', mode: 'strict' } }, fields)
207
+ // { config: { id: 'c1', mode: 'strict' } }
208
+ ```
209
+
210
+ - A field counts as carried when it is an own key whose value is not
211
+ `undefined`. A carried `null` is a real empty value and is captured.
212
+ - When any written field is not carried, the answer is `undefined` and the
213
+ caller offers no Undo at all. A record projected by `$select`, or one the
214
+ server stripped of fields the reader may not read, can lack a written field
215
+ while the server holds a real value for it; a partial or `null` snapshot
216
+ would overwrite that value on Undo.
217
+ - A field the object declares as a relation (`lookup`, `master_detail`,
218
+ `user`, `tree`) is captured as the id it stores, and a `multiple` one as the
219
+ array of ids. `$expand` puts the related record where the id was, and
220
+ writing that record back into the reference is what Undo must never do.
221
+ Which fields are relations is read from `fields`, never from the value's
222
+ shape: a `json` field may hold an object with an `id`.
223
+ - `fields` is required. Pass `undefined` only when the caller has no field
224
+ definitions for the object; nothing is then treated as a relation.
225
+
125
226
  ## Philosophy
126
227
 
127
228
  This package is designed to be **framework-agnostic**. It contains:
@@ -14,7 +14,7 @@
14
14
  * Replaces callback-based patterns with a schema-driven pipeline:
15
15
  * ActionSchema[] → ActionEngine → ActionRunner → ActionResult
16
16
  */
17
- import { ActionRunner, type ActionDef, type ActionContext, type ActionResult } from './ActionRunner';
17
+ import { ActionRunner, type ActionDef, type ActionContext, type ActionResult } from './ActionRunner.js';
18
18
  /**
19
19
  * Action location types — re-exported from `@objectstack/spec/ui` so the
20
20
  * platform has one source of truth (`ACTION_LOCATIONS`). Do NOT redeclare
@@ -118,8 +118,10 @@ export declare class ActionEngine {
118
118
  * DECLARED, which is asked through core's one definition
119
119
  * `hasDeclaredPredicate` (objectui#3850's ruling, adopted here by
120
120
  * objectui#3957). No gate — absent, `''`, blank predicate text in either
121
- * spelling, an envelope with no evaluable `source`, or a value that is
122
- * not a predicate at all — means visible. Evaluator errors hide the
121
+ * spelling, or an envelope whose `source` is `''` — means visible. A
122
+ * value that is present but has no evaluable `source` (`0`, `{}`, an
123
+ * array, an `ast`-only envelope) is a declared gate that faults
124
+ * (objectui#11358), so it hides like any other. Evaluator errors hide the
123
125
  * action (fail-closed) rather than throwing — this matches the contract
124
126
  * used by every individual action renderer (`action-button`,
125
127
  * `action-menu`, `action-bar`, …) so the same action behaves identically
@@ -14,7 +14,7 @@
14
14
  * Replaces callback-based patterns with a schema-driven pipeline:
15
15
  * ActionSchema[] → ActionEngine → ActionRunner → ActionResult
16
16
  */
17
- import { ActionRunner } from './ActionRunner';
17
+ import { ActionRunner } from './ActionRunner.js';
18
18
  import { toPredicateInput } from '../evaluator/predicateInput.js';
19
19
  import { hasDeclaredPredicate } from '../evaluator/declaredPredicate.js';
20
20
  export { ACTION_LOCATIONS } from '@object-ui/types';
@@ -38,7 +38,6 @@ function warnHiddenPredicate(name, raw, err) {
38
38
  return;
39
39
  _warnedPredicates.add(key);
40
40
  const msg = err instanceof Error ? err.message : String(err);
41
- // eslint-disable-next-line no-console
42
41
  console.warn(`[ActionEngine] action "${String(name)}" hidden: its \`visible\` predicate threw — ${msg}. ` +
43
42
  `Predicate: ${src}. Action predicates evaluate against { record, recordId, objectName, user, os.user, … }; ` +
44
43
  `use \`record.<field>\` (not a bare field).`);
@@ -172,8 +171,10 @@ export class ActionEngine {
172
171
  * DECLARED, which is asked through core's one definition
173
172
  * `hasDeclaredPredicate` (objectui#3850's ruling, adopted here by
174
173
  * objectui#3957). No gate — absent, `''`, blank predicate text in either
175
- * spelling, an envelope with no evaluable `source`, or a value that is
176
- * not a predicate at all — means visible. Evaluator errors hide the
174
+ * spelling, or an envelope whose `source` is `''` — means visible. A
175
+ * value that is present but has no evaluable `source` (`0`, `{}`, an
176
+ * array, an `ast`-only envelope) is a declared gate that faults
177
+ * (objectui#11358), so it hides like any other. Evaluator errors hide the
177
178
  * action (fail-closed) rather than throwing — this matches the contract
178
179
  * used by every individual action renderer (`action-button`,
179
180
  * `action-menu`, `action-bar`, …) so the same action behaves identically
@@ -227,10 +228,12 @@ export class ActionEngine {
227
228
  // hid it here because the normalizer wraps a blank string into `'${ }'`
228
229
  // whose verdict is falsy. That is the shape objectui#3314's invariant
229
230
  // forbids, and it is fixed by deleting a range, not by adding one
230
- // (objectui#3957). A value that is not a predicate, or a predicate that
231
- // says nothing, must not be the reason an action is hidden from
232
- // everyone — the same fail-open posture `ActionRunner` already committed
233
- // to for `disabled` (`catch { isDisabled = false }`).
231
+ // (objectui#3957). A predicate that says nothing (blank) must not be
232
+ // the reason an action is hidden from everyone. A value that is not a
233
+ // predicate at all is no longer "nothing" (objectui#11358): it is a
234
+ // declared gate the client cannot evaluate, the normalizer keeps it
235
+ // as such, and the `throwOnError` evaluation below hides it and
236
+ // reports it, as every action renderer's `visible` leg does.
234
237
  //
235
238
  // Booleans need no branch of their own: they are DECLARED (`visible:
236
239
  // false` is a verdict, objectui#3812) and `evaluateCondition`
@@ -242,7 +245,8 @@ export class ActionEngine {
242
245
  // predicate to the legacy JS engine; `toPredicateInput` keeps the
243
246
  // envelope so `evaluateCondition` can route it to `@objectstack/formula`
244
247
  // (the engine the server enforces with), exactly as the renderers do.
245
- // Declared ⇒ the normalizer left something to evaluate, so there is no
248
+ // Declared ⇒ the normalizer left something (possibly the declared-but-
249
+ // unevaluable `cel` envelope, which throws below), so there is no
246
250
  // `undefined` case left to handle here.
247
251
  const expr = toPredicateInput(raw);
248
252
  try {
@@ -262,7 +266,7 @@ export class ActionEngine {
262
266
  // BARE field reference (`done` instead of `record.done`), which is
263
267
  // an undeclared variable in the `{ record, recordId, objectName,
264
268
  // user }` eval scope. Silently hiding it makes that bug invisible
265
- // (the #2183 hunt). Warn once per predicate so it is diagnosable
269
+ // (the objectstack-ai/objectstack#2183 hunt). Warn once per predicate so it is diagnosable
266
270
  // without spamming re-renders.
267
271
  warnHiddenPredicate(ra.action.name, raw, err);
268
272
  return false;