@object-ui/react 17.5.0 → 17.6.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 +334 -0
- package/README.md +9 -8
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.js +434 -22
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/RelatedRecordActionsContext.d.ts +15 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +75 -2
- package/dist/element-data-source/ElementDataSourceGate.js +81 -2
- package/dist/hooks/index.d.ts +35 -35
- package/dist/hooks/index.js +35 -35
- package/dist/hooks/useActionEngine.js +1 -1
- package/dist/hooks/useCapabilityGate.js +6 -5
- package/dist/hooks/useClientNotifications.js +2 -2
- package/dist/hooks/useCrudShortcuts.js +1 -1
- package/dist/hooks/useDatasetDimensionLabels.js +1 -1
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useElementDataSource.js +5 -2
- package/dist/hooks/useExpression.d.ts +24 -3
- package/dist/hooks/useExpression.js +56 -4
- package/dist/hooks/useNavigationOverlay.d.ts +1 -1
- package/dist/hooks/usePageAssignment.js +1 -1
- package/dist/hooks/usePageTransition.js +1 -1
- package/dist/hooks/usePageVariableActionBridge.js +2 -2
- package/dist/hooks/useViewData.js +1 -1
- package/dist/hooks/useViewTransition.js +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +10 -10
- package/dist/schema-input.d.ts +16 -5
- package/dist/schema-input.js +15 -4
- package/dist/spec-bridge/SpecBridge.d.ts +1 -1
- package/dist/spec-bridge/SpecBridge.js +2 -2
- package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
- package/dist/spec-bridge/bridges/form-view.js +51 -7
- package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
- package/dist/spec-bridge/bridges/list-view.js +37 -10
- package/dist/spec-bridge/index.d.ts +4 -4
- package/dist/spec-bridge/index.js +3 -3
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- package/dist/utils/unevaluatedExpression.d.ts +69 -0
- package/dist/utils/unevaluatedExpression.js +194 -0
- package/dist/utils/visibilityDiagnostic.d.ts +119 -0
- package/dist/utils/visibilityDiagnostic.js +284 -0
- package/package.json +7 -7
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
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
|
+
* Dev-build diagnostic: a visibility predicate could not be evaluated
|
|
10
|
+
* (objectui#5454, leg 3 of the 2026-08-21 ruling).
|
|
11
|
+
*
|
|
12
|
+
* ## The defect this names
|
|
13
|
+
*
|
|
14
|
+
* `SchemaRenderer`'s visibility chain is FAIL-SOFT: `evaluateCondition` answers
|
|
15
|
+
* an unevaluable predicate with `true`. On the four negated legs (`visible` /
|
|
16
|
+
* `visibleWhen` / `visibleOn` / `visibility`) that `true` means SHOWN — so "this
|
|
17
|
+
* predicate is broken" and "this predicate said yes" reach the screen as the
|
|
18
|
+
* same pixel. An author who mistyped a root, or who wrote a `record.*` gate
|
|
19
|
+
* before objectui#5454 bound the row, saw their block render and had no signal
|
|
20
|
+
* at all that the gate had not been consulted.
|
|
21
|
+
*
|
|
22
|
+
* One of the three evaluation paths was already loud — a `{ dialect: 'cel' }`
|
|
23
|
+
* envelope routes to `evalFieldPredicate`, which warns (objectstack#5149). The
|
|
24
|
+
* bare-expression and `${…}` template paths were mute. So whether an author
|
|
25
|
+
* heard about their own typo depended on which dialect they happened to write
|
|
26
|
+
* it in, which is the arbitrariness this module removes.
|
|
27
|
+
*
|
|
28
|
+
* ## What it deliberately does NOT do
|
|
29
|
+
*
|
|
30
|
+
* It changes NO verdict. `evaluateCondition` already returned `true` for every
|
|
31
|
+
* unevaluable predicate on every one of its paths, and the caller reproduces
|
|
32
|
+
* exactly that from its catch — including on the two NON-negated legs
|
|
33
|
+
* (`hidden` / `hiddenOn`), where the same `true` means HIDE. Flipping fail-soft
|
|
34
|
+
* to fail-closed is a shipped-behaviour change tracked separately upstream
|
|
35
|
+
* (objectstack#5149, appeal 1, undecided); this is the diagnostic half only.
|
|
36
|
+
*
|
|
37
|
+
* ## Why a separate module
|
|
38
|
+
*
|
|
39
|
+
* Same reason as `unevaluatedExpression.ts` next door: exporting non-components
|
|
40
|
+
* from the module that exports `SchemaRenderer` breaks Fast Refresh
|
|
41
|
+
* (`react-refresh/only-export-components`), and a format/emit split lets the
|
|
42
|
+
* pins assert the words a developer will actually read rather than that a spy
|
|
43
|
+
* was called — a diagnostic whose only test is the latter goes green the moment
|
|
44
|
+
* someone no-ops it.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Prefix every diagnostic line starts with. Exported so tests can match on it
|
|
48
|
+
* and so an app can filter it out of its console transport if it must.
|
|
49
|
+
*/
|
|
50
|
+
export declare const UNRESOLVABLE_VISIBILITY_PREFIX = "[ObjectUI] A visibility predicate could not be evaluated";
|
|
51
|
+
/**
|
|
52
|
+
* Build the message. Separate from the emit so a test can assert the words,
|
|
53
|
+
* not merely that something was logged.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatUnresolvableVisibilityMessage(type: unknown, id: unknown, key: string, raw: unknown, reason: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Dev-build only; the caller applies the gate, so this module is dead code in a
|
|
58
|
+
* production build. `console.warn`, not `error`: the verdict is unchanged and
|
|
59
|
+
* the page still renders, so this is a diagnostic about a predicate — not the
|
|
60
|
+
* refusal `reportUnevaluatedExpressions` emits once raw source has reached the
|
|
61
|
+
* DOM.
|
|
62
|
+
*/
|
|
63
|
+
export declare function reportUnresolvableVisibilityPredicate(type: unknown, id: unknown, key: string, raw: unknown, err: unknown): void;
|
|
64
|
+
/**
|
|
65
|
+
* Test-only reset for the dedupe above — for BOTH legs, which is the point of
|
|
66
|
+
* their sharing one `Set`. The `Set` is module state: without this, the second
|
|
67
|
+
* test to assert the same warning reads the first test's dedupe entry and sees
|
|
68
|
+
* silence — a green run that checked nothing.
|
|
69
|
+
*/
|
|
70
|
+
export declare function __resetVisibilityPredicateWarnings(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Prefix for the objectui#5687 leg. A SIBLING of the constant above, not a
|
|
73
|
+
* reuse of it, and the difference is deliberate.
|
|
74
|
+
*
|
|
75
|
+
* The maintainer ruling (2026-08-22, option A) says this path "emits the
|
|
76
|
+
* dev-only unresolvable-predicate report". What is load-bearing there — and
|
|
77
|
+
* what the dispatch restated — is the reporter's POSTURE: same module, same
|
|
78
|
+
* severity (`console.warn`), same dedupe key shape, same dedupe Set, same
|
|
79
|
+
* dev-only gate at the same call site, same test-only reset. All of that is
|
|
80
|
+
* shared below. The first LINE is not reused, because on this path it would
|
|
81
|
+
* state something false: the predicate did not fail to evaluate. It evaluated
|
|
82
|
+
* perfectly, against the wrong object, and produced a constant. Telling an
|
|
83
|
+
* author "could not be evaluated" would send them hunting for a syntax error
|
|
84
|
+
* that is not there.
|
|
85
|
+
*/
|
|
86
|
+
export declare const ADAPTER_ONLY_DATA_PREDICATE_PREFIX = "[ObjectUI] A visibility predicate resolved `data.*` against the data-source adapter";
|
|
87
|
+
/** Built separately from the emit, for the same reason as the message above. */
|
|
88
|
+
export declare function formatAdapterOnlyDataMessage(type: unknown, id: unknown, key: string, raw: unknown, unresolved: string[]): string;
|
|
89
|
+
/**
|
|
90
|
+
* Dev-build diagnostic for a node-gate predicate that reads `data.*` and gets
|
|
91
|
+
* `undefined` back from the adapter (objectui#5687, maintainer ruling
|
|
92
|
+
* 2026-08-22 option A).
|
|
93
|
+
*
|
|
94
|
+
* ## It changes NO verdict, and it is not allowed to
|
|
95
|
+
*
|
|
96
|
+
* The ruling is explicit that the node tier keeps its documented
|
|
97
|
+
* `data` = adapter semantics: no verdict change, no interpolation change. This
|
|
98
|
+
* function returns `void`, is called for its console output only, and the
|
|
99
|
+
* caller passes the evaluator's answer through untouched. The constant-false
|
|
100
|
+
* still hides the block; the author now hears about it.
|
|
101
|
+
*
|
|
102
|
+
* ## Why it lives beside `reportUnresolvableVisibilityPredicate`
|
|
103
|
+
*
|
|
104
|
+
* They are two halves of ONE silence. objectui#5454 made the THROWING paths
|
|
105
|
+
* loud; measured on this base, a `{ dialect: 'cel' }` envelope reading
|
|
106
|
+
* `data.status` already throws and is already reported by that function. The
|
|
107
|
+
* bare-string and `${…}` template dialects do not throw for the same
|
|
108
|
+
* predicate — they resolve `undefined == 'draft'` to a clean `false` — so the
|
|
109
|
+
* author heard about the identical authoring mistake only if they happened to
|
|
110
|
+
* write it in the CEL dialect. That is the same dialect-dependent arbitrariness
|
|
111
|
+
* objectui#5454 existed to remove, one path further along.
|
|
112
|
+
*
|
|
113
|
+
* Sharing the module means sharing the LIFECYCLE, which is the part that has to
|
|
114
|
+
* match: one dedupe Set, one reset, one severity, one dev-only gate. The dedupe
|
|
115
|
+
* key is tagged with this leg's name so the two diagnostics cannot silence each
|
|
116
|
+
* other for the same (type, key, source) triple — they are different faults,
|
|
117
|
+
* and a node that faults one way is not evidence about the other.
|
|
118
|
+
*/
|
|
119
|
+
export declare function reportAdapterOnlyDataPredicate(type: unknown, id: unknown, key: string, raw: unknown, boundData: unknown): void;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
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
|
+
* Dev-build diagnostic: a visibility predicate could not be evaluated
|
|
10
|
+
* (objectui#5454, leg 3 of the 2026-08-21 ruling).
|
|
11
|
+
*
|
|
12
|
+
* ## The defect this names
|
|
13
|
+
*
|
|
14
|
+
* `SchemaRenderer`'s visibility chain is FAIL-SOFT: `evaluateCondition` answers
|
|
15
|
+
* an unevaluable predicate with `true`. On the four negated legs (`visible` /
|
|
16
|
+
* `visibleWhen` / `visibleOn` / `visibility`) that `true` means SHOWN — so "this
|
|
17
|
+
* predicate is broken" and "this predicate said yes" reach the screen as the
|
|
18
|
+
* same pixel. An author who mistyped a root, or who wrote a `record.*` gate
|
|
19
|
+
* before objectui#5454 bound the row, saw their block render and had no signal
|
|
20
|
+
* at all that the gate had not been consulted.
|
|
21
|
+
*
|
|
22
|
+
* One of the three evaluation paths was already loud — a `{ dialect: 'cel' }`
|
|
23
|
+
* envelope routes to `evalFieldPredicate`, which warns (objectstack#5149). The
|
|
24
|
+
* bare-expression and `${…}` template paths were mute. So whether an author
|
|
25
|
+
* heard about their own typo depended on which dialect they happened to write
|
|
26
|
+
* it in, which is the arbitrariness this module removes.
|
|
27
|
+
*
|
|
28
|
+
* ## What it deliberately does NOT do
|
|
29
|
+
*
|
|
30
|
+
* It changes NO verdict. `evaluateCondition` already returned `true` for every
|
|
31
|
+
* unevaluable predicate on every one of its paths, and the caller reproduces
|
|
32
|
+
* exactly that from its catch — including on the two NON-negated legs
|
|
33
|
+
* (`hidden` / `hiddenOn`), where the same `true` means HIDE. Flipping fail-soft
|
|
34
|
+
* to fail-closed is a shipped-behaviour change tracked separately upstream
|
|
35
|
+
* (objectstack#5149, appeal 1, undecided); this is the diagnostic half only.
|
|
36
|
+
*
|
|
37
|
+
* ## Why a separate module
|
|
38
|
+
*
|
|
39
|
+
* Same reason as `unevaluatedExpression.ts` next door: exporting non-components
|
|
40
|
+
* from the module that exports `SchemaRenderer` breaks Fast Refresh
|
|
41
|
+
* (`react-refresh/only-export-components`), and a format/emit split lets the
|
|
42
|
+
* pins assert the words a developer will actually read rather than that a spy
|
|
43
|
+
* was called — a diagnostic whose only test is the latter goes green the moment
|
|
44
|
+
* someone no-ops it.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Prefix every diagnostic line starts with. Exported so tests can match on it
|
|
48
|
+
* and so an app can filter it out of its console transport if it must.
|
|
49
|
+
*/
|
|
50
|
+
export const UNRESOLVABLE_VISIBILITY_PREFIX = '[ObjectUI] A visibility predicate could not be evaluated';
|
|
51
|
+
/** The raw predicate as an author would recognise it, envelope or not. */
|
|
52
|
+
function predicateSourceText(raw) {
|
|
53
|
+
if (raw && typeof raw === 'object') {
|
|
54
|
+
const source = raw.source;
|
|
55
|
+
if (typeof source === 'string')
|
|
56
|
+
return source;
|
|
57
|
+
}
|
|
58
|
+
return typeof raw === 'string' ? raw : String(raw);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the message. Separate from the emit so a test can assert the words,
|
|
62
|
+
* not merely that something was logged.
|
|
63
|
+
*/
|
|
64
|
+
export function formatUnresolvableVisibilityMessage(type, id, key, raw, reason) {
|
|
65
|
+
const node = typeof type === 'string' && type ? '"' + type + '"' : '(untyped node)';
|
|
66
|
+
const where = typeof id === 'string' && id ? ' (id: "' + id + '")' : '';
|
|
67
|
+
return (UNRESOLVABLE_VISIBILITY_PREFIX + ' - node ' + node + where + '\n' +
|
|
68
|
+
' ' + key + ': ' + JSON.stringify(predicateSourceText(raw)) + '\n' +
|
|
69
|
+
' Reason: ' + reason + '\n' +
|
|
70
|
+
'The node was treated as its safe default, which on this surface means the\n' +
|
|
71
|
+
'gate did NOT bite - a predicate that cannot be evaluated reads on screen\n' +
|
|
72
|
+
'exactly like one that said yes.\n' +
|
|
73
|
+
'Page-component predicates bind `record` (the row on a record page),\n' +
|
|
74
|
+
'`current_user`, and page state as `page.<var>`. Check those roots and the\n' +
|
|
75
|
+
'CEL syntax.');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Reported (node type, key, predicate source) triples, so a re-render — or the
|
|
79
|
+
* post-mount `forceUpdate` that picks up lazy plugin registrations — does not
|
|
80
|
+
* repeat the line. Keyed on the predicate TEXT rather than the schema object:
|
|
81
|
+
* the same broken predicate authored once and rendered over many rows is ONE
|
|
82
|
+
* authoring bug, and an object key would report it once per row.
|
|
83
|
+
*/
|
|
84
|
+
const _warnedVisibilityPredicates = new Set();
|
|
85
|
+
/**
|
|
86
|
+
* Dev-build only; the caller applies the gate, so this module is dead code in a
|
|
87
|
+
* production build. `console.warn`, not `error`: the verdict is unchanged and
|
|
88
|
+
* the page still renders, so this is a diagnostic about a predicate — not the
|
|
89
|
+
* refusal `reportUnevaluatedExpressions` emits once raw source has reached the
|
|
90
|
+
* DOM.
|
|
91
|
+
*/
|
|
92
|
+
export function reportUnresolvableVisibilityPredicate(type, id, key, raw, err) {
|
|
93
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
94
|
+
const dedupeKey = JSON.stringify([type, key, predicateSourceText(raw)]);
|
|
95
|
+
if (_warnedVisibilityPredicates.has(dedupeKey))
|
|
96
|
+
return;
|
|
97
|
+
_warnedVisibilityPredicates.add(dedupeKey);
|
|
98
|
+
console.warn(formatUnresolvableVisibilityMessage(type, id, key, raw, reason));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Test-only reset for the dedupe above — for BOTH legs, which is the point of
|
|
102
|
+
* their sharing one `Set`. The `Set` is module state: without this, the second
|
|
103
|
+
* test to assert the same warning reads the first test's dedupe entry and sees
|
|
104
|
+
* silence — a green run that checked nothing.
|
|
105
|
+
*/
|
|
106
|
+
export function __resetVisibilityPredicateWarnings() {
|
|
107
|
+
_warnedVisibilityPredicates.clear();
|
|
108
|
+
}
|
|
109
|
+
/* -------------------------------------------------------------------------- *
|
|
110
|
+
* objectui#5687 — the NON-throwing half of the same silence
|
|
111
|
+
* -------------------------------------------------------------------------- */
|
|
112
|
+
/**
|
|
113
|
+
* Prefix for the objectui#5687 leg. A SIBLING of the constant above, not a
|
|
114
|
+
* reuse of it, and the difference is deliberate.
|
|
115
|
+
*
|
|
116
|
+
* The maintainer ruling (2026-08-22, option A) says this path "emits the
|
|
117
|
+
* dev-only unresolvable-predicate report". What is load-bearing there — and
|
|
118
|
+
* what the dispatch restated — is the reporter's POSTURE: same module, same
|
|
119
|
+
* severity (`console.warn`), same dedupe key shape, same dedupe Set, same
|
|
120
|
+
* dev-only gate at the same call site, same test-only reset. All of that is
|
|
121
|
+
* shared below. The first LINE is not reused, because on this path it would
|
|
122
|
+
* state something false: the predicate did not fail to evaluate. It evaluated
|
|
123
|
+
* perfectly, against the wrong object, and produced a constant. Telling an
|
|
124
|
+
* author "could not be evaluated" would send them hunting for a syntax error
|
|
125
|
+
* that is not there.
|
|
126
|
+
*/
|
|
127
|
+
export const ADAPTER_ONLY_DATA_PREDICATE_PREFIX = '[ObjectUI] A visibility predicate resolved `data.*` against the data-source adapter';
|
|
128
|
+
/**
|
|
129
|
+
* Strip string literals before scanning for `data.*` reads.
|
|
130
|
+
*
|
|
131
|
+
* The scan below is LEXICAL, not a parse — it reads the predicate's source
|
|
132
|
+
* text. Without this step a predicate that merely quotes the characters
|
|
133
|
+
* (`note == 'data.status'`) would be reported for a read it never performs.
|
|
134
|
+
* That class is not hypothetical in this repo: `examples/schema-catalog`'s
|
|
135
|
+
* `components-complex-scroll-area/code-preview.json` carries `data.features`
|
|
136
|
+
* inside a JS snippet in a `content` string.
|
|
137
|
+
*
|
|
138
|
+
* Replaced with an EMPTY literal of the same quote style rather than deleted,
|
|
139
|
+
* so neighbouring tokens cannot be fused into a new false match.
|
|
140
|
+
*/
|
|
141
|
+
function stripStringLiterals(source) {
|
|
142
|
+
return source.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, "''");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* `data.<path>` reads, matched only where `data` is a ROOT identifier.
|
|
146
|
+
*
|
|
147
|
+
* The leading group is what keeps this from being the loose sweep the card's
|
|
148
|
+
* own cross-reference warns about: `metadata.status` (a longer identifier that
|
|
149
|
+
* ends in `data`) and `record.data.status` (`data` as a MEMBER, which is the
|
|
150
|
+
* row's own field, not this evaluator's root) must not match. Both are pinned.
|
|
151
|
+
* A capture group is used rather than a lookbehind for target-browser reach.
|
|
152
|
+
*/
|
|
153
|
+
const DATA_ROOT_PATH_RE = /(^|[^A-Za-z0-9_$.])data\.([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*)/g;
|
|
154
|
+
/**
|
|
155
|
+
* The `data.*` reads in `source` that are `undefined` on the object `data` is
|
|
156
|
+
* actually bound to — i.e. the ones that make the comparison a constant.
|
|
157
|
+
*
|
|
158
|
+
* ## Why THIS discriminator, measured against the three alternatives
|
|
159
|
+
*
|
|
160
|
+
* The ruling's trigger is not "references `data.`" — a genuine adapter read has
|
|
161
|
+
* to stay silent, and that half is what makes the loud half mean anything.
|
|
162
|
+
* Measured on `packages/core`'s built evaluator, against the four candidate
|
|
163
|
+
* definitions:
|
|
164
|
+
*
|
|
165
|
+
* * "references `data.` at all" — fires on `data.total > 0` against an
|
|
166
|
+
* adapter that HAS `total`. Refused by the ruling in as many words.
|
|
167
|
+
* * "the whole predicate is constant-false" — measured: `data.total > 100`
|
|
168
|
+
* against `{ total: 99 }` returns `false`, and so does a correct
|
|
169
|
+
* `record.status == 'draft'` against a row whose status is `open`. The
|
|
170
|
+
* definition cannot tell a gate that correctly says NO from a gate that
|
|
171
|
+
* never asked. It would report every hiding gate in the repo.
|
|
172
|
+
* * "the comparison OPERAND is undefined" — the most precise reading, and it
|
|
173
|
+
* needs the expression engine to expose its operands. That is a change to
|
|
174
|
+
* `@object-ui/core`'s evaluator for a diagnostic on a card that dissolves
|
|
175
|
+
* when objectui#5330's deprecation window closes.
|
|
176
|
+
* * "a `data.*` read the bound object does not answer" — this one. On the
|
|
177
|
+
* card's repro (`data.status` against the adapter) it fires; on
|
|
178
|
+
* `${data.total}` against `{ total: 99 }` it does not; and it is
|
|
179
|
+
* STRUCTURALLY incapable of firing on the `record.*` bucket (objectui#5401
|
|
180
|
+
* → #5454), which is the adjacent card with a different correct fix.
|
|
181
|
+
*
|
|
182
|
+
* Residue, stated rather than hidden: deliberate absence idioms
|
|
183
|
+
* (`data.status == null`, `!data.status`) are reported. They are not really
|
|
184
|
+
* false positives at this tier — an adapter that has no `status` makes those
|
|
185
|
+
* constants too, just constant-TRUE instead of constant-false — but they are
|
|
186
|
+
* the shapes an author could have meant.
|
|
187
|
+
*
|
|
188
|
+
* The walk mirrors `useDataScope`'s own `path.split('.').reduce(…)` resolution
|
|
189
|
+
* of a `data.*` path, so "undefined here" means what it means everywhere else
|
|
190
|
+
* this repo resolves against the adapter.
|
|
191
|
+
*/
|
|
192
|
+
function unresolvedDataPaths(source, boundData) {
|
|
193
|
+
const scannable = stripStringLiterals(source);
|
|
194
|
+
const out = [];
|
|
195
|
+
DATA_ROOT_PATH_RE.lastIndex = 0;
|
|
196
|
+
let match;
|
|
197
|
+
while ((match = DATA_ROOT_PATH_RE.exec(scannable)) !== null) {
|
|
198
|
+
const path = match[2];
|
|
199
|
+
let cursor = boundData;
|
|
200
|
+
try {
|
|
201
|
+
for (const segment of path.split('.')) {
|
|
202
|
+
if (cursor === null || cursor === undefined) {
|
|
203
|
+
cursor = undefined;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
cursor = cursor[segment];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// A throwing getter on the adapter is the OTHER reporter's case: the
|
|
211
|
+
// evaluator would have thrown too. Stay silent rather than guess.
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const spelling = 'data.' + path;
|
|
215
|
+
if (cursor === undefined && out.indexOf(spelling) === -1)
|
|
216
|
+
out.push(spelling);
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
/** Built separately from the emit, for the same reason as the message above. */
|
|
221
|
+
export function formatAdapterOnlyDataMessage(type, id, key, raw, unresolved) {
|
|
222
|
+
const node = typeof type === 'string' && type ? '"' + type + '"' : '(untyped node)';
|
|
223
|
+
const where = typeof id === 'string' && id ? ' (id: "' + id + '")' : '';
|
|
224
|
+
return (ADAPTER_ONLY_DATA_PREDICATE_PREFIX + ' - node ' + node + where + '\n' +
|
|
225
|
+
' ' + key + ': ' + JSON.stringify(predicateSourceText(raw)) + '\n' +
|
|
226
|
+
' Undefined on the adapter: ' + unresolved.join(', ') + '\n' +
|
|
227
|
+
'At the node tier `data` is the DATA-SOURCE ADAPTER - the object\n' +
|
|
228
|
+
'`${data.total}` in a props bag reads - and it is NOT the row. The reads\n' +
|
|
229
|
+
'above are undefined on it, so this predicate is a CONSTANT: it does not\n' +
|
|
230
|
+
'depend on the row at all, and on this surface a constant `false` hides the\n' +
|
|
231
|
+
'node on every row while looking exactly like a gate that said no.\n' +
|
|
232
|
+
'Write the row as `record.*` (e.g. `record.status`), which page-component\n' +
|
|
233
|
+
'predicates bind alongside `current_user` and page state as `page.<var>`.\n' +
|
|
234
|
+
'`data.*` as a spelling for the row is deprecated (objectui#5330) and was\n' +
|
|
235
|
+
'never bound to the row on this tier.');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Dev-build diagnostic for a node-gate predicate that reads `data.*` and gets
|
|
239
|
+
* `undefined` back from the adapter (objectui#5687, maintainer ruling
|
|
240
|
+
* 2026-08-22 option A).
|
|
241
|
+
*
|
|
242
|
+
* ## It changes NO verdict, and it is not allowed to
|
|
243
|
+
*
|
|
244
|
+
* The ruling is explicit that the node tier keeps its documented
|
|
245
|
+
* `data` = adapter semantics: no verdict change, no interpolation change. This
|
|
246
|
+
* function returns `void`, is called for its console output only, and the
|
|
247
|
+
* caller passes the evaluator's answer through untouched. The constant-false
|
|
248
|
+
* still hides the block; the author now hears about it.
|
|
249
|
+
*
|
|
250
|
+
* ## Why it lives beside `reportUnresolvableVisibilityPredicate`
|
|
251
|
+
*
|
|
252
|
+
* They are two halves of ONE silence. objectui#5454 made the THROWING paths
|
|
253
|
+
* loud; measured on this base, a `{ dialect: 'cel' }` envelope reading
|
|
254
|
+
* `data.status` already throws and is already reported by that function. The
|
|
255
|
+
* bare-string and `${…}` template dialects do not throw for the same
|
|
256
|
+
* predicate — they resolve `undefined == 'draft'` to a clean `false` — so the
|
|
257
|
+
* author heard about the identical authoring mistake only if they happened to
|
|
258
|
+
* write it in the CEL dialect. That is the same dialect-dependent arbitrariness
|
|
259
|
+
* objectui#5454 existed to remove, one path further along.
|
|
260
|
+
*
|
|
261
|
+
* Sharing the module means sharing the LIFECYCLE, which is the part that has to
|
|
262
|
+
* match: one dedupe Set, one reset, one severity, one dev-only gate. The dedupe
|
|
263
|
+
* key is tagged with this leg's name so the two diagnostics cannot silence each
|
|
264
|
+
* other for the same (type, key, source) triple — they are different faults,
|
|
265
|
+
* and a node that faults one way is not evidence about the other.
|
|
266
|
+
*/
|
|
267
|
+
export function reportAdapterOnlyDataPredicate(type, id, key, raw, boundData) {
|
|
268
|
+
const source = predicateSourceText(raw);
|
|
269
|
+
// Fast reject: the overwhelming majority of predicates never mention `data.`
|
|
270
|
+
// at all, and this runs for every visibility predicate of every node in dev.
|
|
271
|
+
if (source.indexOf('data.') === -1)
|
|
272
|
+
return;
|
|
273
|
+
const unresolved = unresolvedDataPaths(source, boundData);
|
|
274
|
+
// Every `data.*` read the adapter answers -> a genuine adapter read.
|
|
275
|
+
// SILENT. This is the half of the ruling that makes the other half mean
|
|
276
|
+
// something.
|
|
277
|
+
if (unresolved.length === 0)
|
|
278
|
+
return;
|
|
279
|
+
const dedupeKey = JSON.stringify(['adapter-only-data', type, key, source]);
|
|
280
|
+
if (_warnedVisibilityPredicates.has(dedupeKey))
|
|
281
|
+
return;
|
|
282
|
+
_warnedVisibilityPredicates.add(dedupeKey);
|
|
283
|
+
console.warn(formatAdapterOnlyDataMessage(type, id, key, raw, unresolved));
|
|
284
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/react",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "React bindings and SchemaRenderer component for Object UI",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@objectstack/spec": "^17.0.0
|
|
28
|
-
"react-hook-form": "^7.
|
|
29
|
-
"@object-ui/core": "17.
|
|
30
|
-
"@object-ui/
|
|
31
|
-
"@object-ui/
|
|
32
|
-
"@object-ui/
|
|
27
|
+
"@objectstack/spec": "^17.0.0",
|
|
28
|
+
"react-hook-form": "^7.85.0",
|
|
29
|
+
"@object-ui/core": "17.6.0",
|
|
30
|
+
"@object-ui/i18n": "17.6.0",
|
|
31
|
+
"@object-ui/types": "17.6.0",
|
|
32
|
+
"@object-ui/data-objectstack": "17.6.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0",
|