@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.
- package/CHANGELOG.md +7260 -0
- package/README.md +228 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +473 -69
- package/dist/actions/ActionRunner.js +792 -125
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/UndoManager.d.ts +8 -13
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +67 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/navigationTarget.d.ts +41 -0
- package/dist/actions/navigationTarget.js +20 -0
- package/dist/actions/recordDelete.d.ts +154 -0
- package/dist/actions/recordDelete.js +110 -0
- package/dist/adapters/ApiDataSource.d.ts +8 -4
- package/dist/adapters/ApiDataSource.js +19 -7
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/adapters/resolveDataSource.d.ts +9 -6
- package/dist/adapters/resolveDataSource.js +9 -12
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +8 -25
- package/dist/data-scope/ViewDataProvider.js +27 -54
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/deadlineSettled.d.ts +40 -0
- package/dist/evaluator/deadlineSettled.js +45 -0
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +208 -31
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- package/dist/evaluator/subjectPermissions.d.ts +105 -0
- package/dist/evaluator/subjectPermissions.js +86 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +83 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +382 -34
- package/dist/registry/Registry.js +247 -12
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/utils/affordanceGrants.d.ts +371 -0
- package/dist/utils/affordanceGrants.js +252 -0
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +165 -5
- package/dist/utils/chart-series.js +123 -18
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +389 -46
- package/dist/utils/date-display.d.ts +346 -0
- package/dist/utils/date-display.js +644 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +81 -9
- package/dist/utils/expand-fields.js +81 -9
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +158 -6
- package/dist/utils/filter-converter.js +1098 -24
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +135 -11
- package/dist/utils/normalize-list-view.js +243 -64
- package/dist/utils/number-display.d.ts +63 -14
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +45 -12
- package/dist/utils/predicate-fields.js +141 -12
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/sort-query.d.ts +115 -15
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +225 -83
- package/dist/validation/server-owned-value.d.ts +5 -1
- package/dist/validation/server-owned-value.js +5 -1
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -89,3 +89,81 @@ export declare function computeDrillFilter(config: DrillDownConfig | undefined,
|
|
|
89
89
|
export declare function resolveDrillTitle(config: DrillDownConfig | undefined, event: DrillEvent, fallback?: string): string;
|
|
90
90
|
/** Whether drill-down is enabled on a config (treats `{}` as enabled too). */
|
|
91
91
|
export declare function isDrillEnabled(config: DrillDownConfig | undefined): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Compose a widget's OWN filter with the filter a drill click derived, into the
|
|
94
|
+
* one filter the drilled list is scoped by.
|
|
95
|
+
*
|
|
96
|
+
* ## The rule: `widget.filter ∧ drill.filter`
|
|
97
|
+
*
|
|
98
|
+
* The two are independent filter SOURCES and a drill must satisfy BOTH. The
|
|
99
|
+
* widget's filter is what scopes the chart; the click context only says WHICH
|
|
100
|
+
* bucket of that scope the user asked to see. So a drill may narrow the widget's
|
|
101
|
+
* scope and may never widen it — which makes the composition a conjunction, not
|
|
102
|
+
* a merge and emphatically not a spread.
|
|
103
|
+
*
|
|
104
|
+
* ⛔ The rule is NOT invented here. It is the contract {@link mergeFilterNodes}
|
|
105
|
+
* already states — "combine filter sources under a single `and`, each as its OWN
|
|
106
|
+
* child" — the sink every other multi-source filter in this repo goes through
|
|
107
|
+
* (`ObjectView`, `RelatedList`, `LineItemsPanel`, `RecordPickerDialog`,
|
|
108
|
+
* `ElementDataSourceGate`, `buildEffectiveFilter`). This function only applies
|
|
109
|
+
* it at the drill seam and names it, so the answer is in one place rather than
|
|
110
|
+
* re-derived per widget.
|
|
111
|
+
*
|
|
112
|
+
* ## Why the two arms needed a sink at all (objectui#8944)
|
|
113
|
+
*
|
|
114
|
+
* `ObjectChartSchema.filter` admits BOTH a spec `FilterArray`
|
|
115
|
+
* (`[['stage','=','won']]`) and the ObjectQL `$filter` object
|
|
116
|
+
* (`{ close_date: { $gte } }`), because both are read — both are forwarded
|
|
117
|
+
* verbatim to `ds.aggregate` / `ds.find`. The drill seam used to compose them by
|
|
118
|
+
* SPREADING the widget's filter into an object literal, which is correct for the
|
|
119
|
+
* object arm and silent nonsense for the array arm: spreading `[['stage','=',
|
|
120
|
+
* 'won']]` yields the index key `{ '0': ['stage','=','won'] }`, so the widget's
|
|
121
|
+
* own conditions were replaced by a key the query layer ignores and the drilled
|
|
122
|
+
* list showed rows the chart itself was scoped to exclude. `toFilterNode` (via
|
|
123
|
+
* `mergeFilterNodes`) already lowers all three shapes in circulation, so routing
|
|
124
|
+
* the pair through it is what makes the array arm survive.
|
|
125
|
+
*
|
|
126
|
+
* ## Why the result is lowered back to the object dialect
|
|
127
|
+
*
|
|
128
|
+
* {@link mergeFilterNodes} answers in the ObjectQL AST
|
|
129
|
+
* (`['and', <widget>, <drill>]`). Both drill sinks take the `FilterCondition`
|
|
130
|
+
* OBJECT dialect instead — the drawer hands the value to `object-data-table`'s
|
|
131
|
+
* `filter` (which becomes `$filter`), and `DrillNavigationContext.openRecordList`
|
|
132
|
+
* declares `Record<string, unknown>` and serializes it to `filter[...]` URL
|
|
133
|
+
* params. `parseFilterAST` is the spec's single lowering sink between the two
|
|
134
|
+
* dialects, so it is what converts, rather than a second local translation.
|
|
135
|
+
*
|
|
136
|
+
* ⭐ A lone surviving source lowers back to exactly the flat object the spread
|
|
137
|
+
* produced (`{ stage: 'won' }`), so a chart with no filter of its own drills
|
|
138
|
+
* identically to before; only a genuinely composed pair gains the `$and`.
|
|
139
|
+
*
|
|
140
|
+
* Returns `undefined` when neither source carries anything, so callers can omit
|
|
141
|
+
* the key rather than send an empty filter.
|
|
142
|
+
*
|
|
143
|
+
* ⚠️ Refusals are the sink's, not this seam's: `mergeFilterNodes` rejects a
|
|
144
|
+
* comparand the wire would also reject (a bare array on `=`, a `RegExp`) with
|
|
145
|
+
* the `INVALID_FILTER` / 400 envelope. Such a filter already fails the widget's
|
|
146
|
+
* OWN query for the same reason, so the drill and the chart now agree instead of
|
|
147
|
+
* the drill quietly sending something the chart could not.
|
|
148
|
+
*
|
|
149
|
+
* ## One refusal type out of this seam (objectui#10789)
|
|
150
|
+
*
|
|
151
|
+
* The seam has TWO refusing steps, and they used to throw two different
|
|
152
|
+
* things. `mergeFilterNodes` throws a {@link FilterOperatorError}. The spec's
|
|
153
|
+
* `parseFilterAST`, which judges comparand shapes the converter passes
|
|
154
|
+
* through, throws a plain `Error` that carries the same envelope as own
|
|
155
|
+
* properties: `code: 'INVALID_FILTER'` and `status: 400`. An array-dialect
|
|
156
|
+
* widget filter holds no rule objects, so the converter never judges it, and
|
|
157
|
+
* `[['stage', 'in', 'won']]` (a scalar on a list operator) is refused only
|
|
158
|
+
* there. So is the object spelling `{ stage: { $in: 'won' } }`.
|
|
159
|
+
*
|
|
160
|
+
* Every caller catches the refusal as a `FilterOperatorError`: the chart,
|
|
161
|
+
* pivot and dataset drills report it and open nothing (never a filter-less
|
|
162
|
+
* drill). So the spec's refusal is re-raised here AS one, with its message
|
|
163
|
+
* verbatim and the same `INVALID_FILTER` / 400 envelope, rather than taught to
|
|
164
|
+
* each caller as a second shape. Only an error whose `code` is
|
|
165
|
+
* `INVALID_FILTER` is re-raised; anything else from `parseFilterAST` is a
|
|
166
|
+
* defect rather than a statement about the author's filter, and passes
|
|
167
|
+
* through untouched.
|
|
168
|
+
*/
|
|
169
|
+
export declare function composeDrillFilter(widgetFilter: unknown, drillFilter: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
package/dist/utils/drill-down.js
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* The helpers are intentionally protocol-agnostic — any caller can
|
|
22
22
|
* synthesize an `event` object and rely on the same defaults / templating.
|
|
23
23
|
*/
|
|
24
|
+
import { parseFilterAST } from '@objectstack/spec/data';
|
|
25
|
+
import { FilterOperatorError, mergeFilterNodes } from './filter-converter.js';
|
|
24
26
|
const TEMPLATE = /\$\{event\.([a-zA-Z0-9_]+)\}/g;
|
|
25
27
|
/**
|
|
26
28
|
* Substitute `${event.x}` placeholders in a string with values from the
|
|
@@ -137,3 +139,94 @@ export function isDrillEnabled(config) {
|
|
|
137
139
|
return false;
|
|
138
140
|
return config.enabled !== false;
|
|
139
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Compose a widget's OWN filter with the filter a drill click derived, into the
|
|
144
|
+
* one filter the drilled list is scoped by.
|
|
145
|
+
*
|
|
146
|
+
* ## The rule: `widget.filter ∧ drill.filter`
|
|
147
|
+
*
|
|
148
|
+
* The two are independent filter SOURCES and a drill must satisfy BOTH. The
|
|
149
|
+
* widget's filter is what scopes the chart; the click context only says WHICH
|
|
150
|
+
* bucket of that scope the user asked to see. So a drill may narrow the widget's
|
|
151
|
+
* scope and may never widen it — which makes the composition a conjunction, not
|
|
152
|
+
* a merge and emphatically not a spread.
|
|
153
|
+
*
|
|
154
|
+
* ⛔ The rule is NOT invented here. It is the contract {@link mergeFilterNodes}
|
|
155
|
+
* already states — "combine filter sources under a single `and`, each as its OWN
|
|
156
|
+
* child" — the sink every other multi-source filter in this repo goes through
|
|
157
|
+
* (`ObjectView`, `RelatedList`, `LineItemsPanel`, `RecordPickerDialog`,
|
|
158
|
+
* `ElementDataSourceGate`, `buildEffectiveFilter`). This function only applies
|
|
159
|
+
* it at the drill seam and names it, so the answer is in one place rather than
|
|
160
|
+
* re-derived per widget.
|
|
161
|
+
*
|
|
162
|
+
* ## Why the two arms needed a sink at all (objectui#8944)
|
|
163
|
+
*
|
|
164
|
+
* `ObjectChartSchema.filter` admits BOTH a spec `FilterArray`
|
|
165
|
+
* (`[['stage','=','won']]`) and the ObjectQL `$filter` object
|
|
166
|
+
* (`{ close_date: { $gte } }`), because both are read — both are forwarded
|
|
167
|
+
* verbatim to `ds.aggregate` / `ds.find`. The drill seam used to compose them by
|
|
168
|
+
* SPREADING the widget's filter into an object literal, which is correct for the
|
|
169
|
+
* object arm and silent nonsense for the array arm: spreading `[['stage','=',
|
|
170
|
+
* 'won']]` yields the index key `{ '0': ['stage','=','won'] }`, so the widget's
|
|
171
|
+
* own conditions were replaced by a key the query layer ignores and the drilled
|
|
172
|
+
* list showed rows the chart itself was scoped to exclude. `toFilterNode` (via
|
|
173
|
+
* `mergeFilterNodes`) already lowers all three shapes in circulation, so routing
|
|
174
|
+
* the pair through it is what makes the array arm survive.
|
|
175
|
+
*
|
|
176
|
+
* ## Why the result is lowered back to the object dialect
|
|
177
|
+
*
|
|
178
|
+
* {@link mergeFilterNodes} answers in the ObjectQL AST
|
|
179
|
+
* (`['and', <widget>, <drill>]`). Both drill sinks take the `FilterCondition`
|
|
180
|
+
* OBJECT dialect instead — the drawer hands the value to `object-data-table`'s
|
|
181
|
+
* `filter` (which becomes `$filter`), and `DrillNavigationContext.openRecordList`
|
|
182
|
+
* declares `Record<string, unknown>` and serializes it to `filter[...]` URL
|
|
183
|
+
* params. `parseFilterAST` is the spec's single lowering sink between the two
|
|
184
|
+
* dialects, so it is what converts, rather than a second local translation.
|
|
185
|
+
*
|
|
186
|
+
* ⭐ A lone surviving source lowers back to exactly the flat object the spread
|
|
187
|
+
* produced (`{ stage: 'won' }`), so a chart with no filter of its own drills
|
|
188
|
+
* identically to before; only a genuinely composed pair gains the `$and`.
|
|
189
|
+
*
|
|
190
|
+
* Returns `undefined` when neither source carries anything, so callers can omit
|
|
191
|
+
* the key rather than send an empty filter.
|
|
192
|
+
*
|
|
193
|
+
* ⚠️ Refusals are the sink's, not this seam's: `mergeFilterNodes` rejects a
|
|
194
|
+
* comparand the wire would also reject (a bare array on `=`, a `RegExp`) with
|
|
195
|
+
* the `INVALID_FILTER` / 400 envelope. Such a filter already fails the widget's
|
|
196
|
+
* OWN query for the same reason, so the drill and the chart now agree instead of
|
|
197
|
+
* the drill quietly sending something the chart could not.
|
|
198
|
+
*
|
|
199
|
+
* ## One refusal type out of this seam (objectui#10789)
|
|
200
|
+
*
|
|
201
|
+
* The seam has TWO refusing steps, and they used to throw two different
|
|
202
|
+
* things. `mergeFilterNodes` throws a {@link FilterOperatorError}. The spec's
|
|
203
|
+
* `parseFilterAST`, which judges comparand shapes the converter passes
|
|
204
|
+
* through, throws a plain `Error` that carries the same envelope as own
|
|
205
|
+
* properties: `code: 'INVALID_FILTER'` and `status: 400`. An array-dialect
|
|
206
|
+
* widget filter holds no rule objects, so the converter never judges it, and
|
|
207
|
+
* `[['stage', 'in', 'won']]` (a scalar on a list operator) is refused only
|
|
208
|
+
* there. So is the object spelling `{ stage: { $in: 'won' } }`.
|
|
209
|
+
*
|
|
210
|
+
* Every caller catches the refusal as a `FilterOperatorError`: the chart,
|
|
211
|
+
* pivot and dataset drills report it and open nothing (never a filter-less
|
|
212
|
+
* drill). So the spec's refusal is re-raised here AS one, with its message
|
|
213
|
+
* verbatim and the same `INVALID_FILTER` / 400 envelope, rather than taught to
|
|
214
|
+
* each caller as a second shape. Only an error whose `code` is
|
|
215
|
+
* `INVALID_FILTER` is re-raised; anything else from `parseFilterAST` is a
|
|
216
|
+
* defect rather than a statement about the author's filter, and passes
|
|
217
|
+
* through untouched.
|
|
218
|
+
*/
|
|
219
|
+
export function composeDrillFilter(widgetFilter, drillFilter) {
|
|
220
|
+
const merged = mergeFilterNodes(widgetFilter, drillFilter);
|
|
221
|
+
try {
|
|
222
|
+
// `FilterCondition` is the spec's object-dialect filter; the drill sinks
|
|
223
|
+
// type the same value as `Record<string, unknown>`, and this is the one
|
|
224
|
+
// seam where the two names meet.
|
|
225
|
+
return parseFilterAST(merged);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error?.code !== 'INVALID_FILTER')
|
|
229
|
+
throw error;
|
|
230
|
+
throw new FilterOperatorError(error.message);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -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
|
|
11
|
-
*
|
|
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
|
|
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 `
|
|
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,34 @@
|
|
|
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/fields/src/resolveActionParams.ts` (objectui#5874; moved out of
|
|
102
|
+
* app-shell by objectui#12105 so the bulk dialog shares it);
|
|
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.
|
|
56
127
|
*
|
|
57
128
|
* The third one used to be a second hand-maintained copy, and this comment used
|
|
58
129
|
* to claim the set "mirrors the form layer's `DATA_SOURCE_FIELD_TYPES`
|
|
@@ -64,10 +135,11 @@
|
|
|
64
135
|
* (objectui#4790). The form now DERIVES from this set instead of restating it:
|
|
65
136
|
*
|
|
66
137
|
* form's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
67
|
-
* +
|
|
138
|
+
* + the widget-hint-only picker names
|
|
68
139
|
*
|
|
69
140
|
* The extension is one-directional and cannot fold back into this set:
|
|
70
|
-
* `object-ref` / `filter-condition` / `recipient-picker`
|
|
141
|
+
* `object-ref` / `filter-condition` / `recipient-picker` (and the registry-name
|
|
142
|
+
* pickers `position-ref` / `permission-ref`, objectui#7611) are widget HINTS, never
|
|
71
143
|
* declarable field types (`widgetHintOnly: true` in
|
|
72
144
|
* `app-shell/src/utils/paramValueShape.ts`), so no object schema can produce a
|
|
73
145
|
* field whose `type` is one of them.
|
|
@@ -76,7 +148,7 @@
|
|
|
76
148
|
* of the above (`lookup` / `master_detail` / `user` — no `tree`, no pickers, and
|
|
77
149
|
* for a while a fifth spelling, `owner`, that objectui#4814 retired). It now
|
|
78
150
|
* derives from this set with NO extension, because the bulk dialog's params
|
|
79
|
-
* cannot produce the
|
|
151
|
+
* cannot produce the widget-hint names (objectui#4815):
|
|
80
152
|
*
|
|
81
153
|
* bulk dialog's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
82
154
|
*
|
|
@@ -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
|
|
12
|
-
*
|
|
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
|
|
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 `
|
|
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,34 @@ 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/fields/src/resolveActionParams.ts` (objectui#5874; moved out of
|
|
103
|
+
* app-shell by objectui#12105 so the bulk dialog shares it);
|
|
104
|
+
* - the detail header's relation test — `HeaderHighlight.tsx` in
|
|
105
|
+
* `packages/plugin-detail` (objectui#5874);
|
|
106
|
+
* - the detail drawer's relation test — `RecordDetailDrawer.tsx` in
|
|
107
|
+
* `packages/plugin-detail`, reading through `isExpandableFieldType`
|
|
108
|
+
* (objectui#5874);
|
|
109
|
+
* - the kanban board's `$expand` — `ObjectKanban.tsx` in
|
|
110
|
+
* `packages/plugin-kanban`, reading through `buildExpandFields`
|
|
111
|
+
* (objectui#5874).
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ Re-derive rather than trust. One command, from the repo root, over the
|
|
114
|
+
* whole tree with NO pathspec. (Narrowing it to `packages/` + a `*` segment +
|
|
115
|
+
* `/src` matches ZERO files, and would drop `apps/` besides. The two-character
|
|
116
|
+
* sequence is spelled out in words here because writing it literally would end
|
|
117
|
+
* this comment.)
|
|
118
|
+
*
|
|
119
|
+
* git grep -lE 'EXPANDABLE_FIELD_TYPES|isExpandableFieldType|buildExpandFields'
|
|
120
|
+
*
|
|
121
|
+
* 96 files on `da5e4f69e`. Drop tests, changesets and changelogs and the
|
|
122
|
+
* production readers fall into three radii, only the first of which the list
|
|
123
|
+
* above is about: this file plus 8 more naming the set directly, plus 4 more
|
|
124
|
+
* calling `isExpandableFieldType`, plus 13 more calling `buildExpandFields`.
|
|
125
|
+
* The sweep was controlled by planting a reader under `apps/console/src/` and
|
|
126
|
+
* confirming the same command found it — `apps/` holds no reader today, and a
|
|
127
|
+
* zero-hit region is otherwise indistinguishable from a blind spot.
|
|
57
128
|
*
|
|
58
129
|
* The third one used to be a second hand-maintained copy, and this comment used
|
|
59
130
|
* to claim the set "mirrors the form layer's `DATA_SOURCE_FIELD_TYPES`
|
|
@@ -65,10 +136,11 @@ import { columnIdentity } from './column-identity.js';
|
|
|
65
136
|
* (objectui#4790). The form now DERIVES from this set instead of restating it:
|
|
66
137
|
*
|
|
67
138
|
* form's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
68
|
-
* +
|
|
139
|
+
* + the widget-hint-only picker names
|
|
69
140
|
*
|
|
70
141
|
* The extension is one-directional and cannot fold back into this set:
|
|
71
|
-
* `object-ref` / `filter-condition` / `recipient-picker`
|
|
142
|
+
* `object-ref` / `filter-condition` / `recipient-picker` (and the registry-name
|
|
143
|
+
* pickers `position-ref` / `permission-ref`, objectui#7611) are widget HINTS, never
|
|
72
144
|
* declarable field types (`widgetHintOnly: true` in
|
|
73
145
|
* `app-shell/src/utils/paramValueShape.ts`), so no object schema can produce a
|
|
74
146
|
* field whose `type` is one of them.
|
|
@@ -77,7 +149,7 @@ import { columnIdentity } from './column-identity.js';
|
|
|
77
149
|
* of the above (`lookup` / `master_detail` / `user` — no `tree`, no pickers, and
|
|
78
150
|
* for a while a fifth spelling, `owner`, that objectui#4814 retired). It now
|
|
79
151
|
* derives from this set with NO extension, because the bulk dialog's params
|
|
80
|
-
* cannot produce the
|
|
152
|
+
* cannot produce the widget-hint names (objectui#4815):
|
|
81
153
|
*
|
|
82
154
|
* bulk dialog's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
83
155
|
*
|