@object-ui/react 17.6.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3766 -0
- package/README.md +294 -38
- package/dist/SchemaRenderer.d.ts +38 -7
- package/dist/SchemaRenderer.js +1218 -87
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/DiscussionContext.d.ts +12 -0
- package/dist/context/DiscussionContext.js +2 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/InlineEditContext.d.ts +81 -1
- package/dist/context/InlineEditContext.js +8 -2
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +5 -1
- package/dist/context/SchemaRendererContext.d.ts +54 -2
- package/dist/context/SchemaRendererContext.js +38 -5
- package/dist/context/ThemeContext.js +1 -1
- package/dist/context/actionRunnerTranslator.d.ts +27 -0
- package/dist/context/actionRunnerTranslator.js +37 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +88 -1
- package/dist/element-data-source/ElementDataSourceGate.js +326 -13
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.js +10 -1
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +21 -8
- package/dist/hooks/useActionTextLocalizer.js +33 -8
- package/dist/hooks/useConfigBagEvaluator.d.ts +30 -0
- package/dist/hooks/useConfigBagEvaluator.js +39 -0
- package/dist/hooks/useDensityMode.d.ts +6 -2
- package/dist/hooks/useDensityMode.js +6 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +24 -7
- package/dist/hooks/useExpression.d.ts +52 -59
- package/dist/hooks/useExpression.js +58 -90
- package/dist/hooks/useFilterScope.d.ts +22 -2
- package/dist/hooks/useFilterScope.js +29 -4
- package/dist/hooks/useGlobalUndo.d.ts +10 -0
- package/dist/hooks/useGlobalUndo.js +112 -46
- package/dist/hooks/useNavigationOverlay.d.ts +76 -11
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +8 -5
- package/dist/hooks/usePageAssignment.js +37 -16
- package/dist/hooks/useRecordSearch.d.ts +34 -1
- package/dist/hooks/useRecordSearch.js +42 -7
- package/dist/hooks/useResolvedFilter.d.ts +46 -0
- package/dist/hooks/useResolvedFilter.js +130 -0
- package/dist/hooks/useSchemaPersistence.d.ts +7 -0
- package/dist/hooks/useSchemaPersistence.js +144 -5
- package/dist/hooks/useSettledSchema.d.ts +123 -0
- package/dist/hooks/useSettledSchema.js +186 -0
- package/dist/hooks/useViewData.d.ts +7 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +45 -1
- package/dist/schema-input.d.ts +38 -8
- package/dist/schema-input.js +39 -7
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +1 -1
- package/dist/utils/error-message.js +1 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/nonGridRowCeiling.d.ts +44 -0
- package/dist/utils/nonGridRowCeiling.js +78 -0
- package/dist/utils/paramsBag.d.ts +28 -0
- package/dist/utils/paramsBag.js +146 -0
- package/dist/utils/propsBagDiagnostic.d.ts +196 -0
- package/dist/utils/propsBagDiagnostic.js +421 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +42 -1
- package/dist/utils/unevaluatedExpression.js +74 -11
- package/dist/utils/visibilityDiagnostic.d.ts +271 -19
- package/dist/utils/visibilityDiagnostic.js +311 -44
- package/package.json +10 -10
- package/dist/hooks/useClientNotifications.d.ts +0 -47
- package/dist/hooks/useClientNotifications.js +0 -162
- package/dist/spec-bridge/SpecBridge.d.ts +0 -28
- package/dist/spec-bridge/SpecBridge.js +0 -56
- package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
- package/dist/spec-bridge/bridges/form-view.js +0 -170
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -268
- package/dist/spec-bridge/index.d.ts +0 -11
- package/dist/spec-bridge/index.js +0 -10
- package/dist/spec-bridge/types.d.ts +0 -55
- package/dist/spec-bridge/types.js +0 -8
|
@@ -0,0 +1,421 @@
|
|
|
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
|
+
import { isConfigBag } from './configBag.js';
|
|
9
|
+
/**
|
|
10
|
+
* Dev-build diagnostic: a `props` CONFIG BAG on a node whose renderer reads its
|
|
11
|
+
* config from `schema` — so the keys inside it are evaluated, spread as React
|
|
12
|
+
* props, and then never read (objectui#6708).
|
|
13
|
+
*
|
|
14
|
+
* ## The defect this names
|
|
15
|
+
*
|
|
16
|
+
* `SchemaRenderer` HOISTS every `properties.*` value onto the node (minus
|
|
17
|
+
* `HOIST_PROTECTED_KEYS`), so a key written under `properties` is a real value
|
|
18
|
+
* on `schema.<key>` by the time a renderer destructures it. `props` is NOT
|
|
19
|
+
* hoisted: it is spread as React props on the created element. A renderer
|
|
20
|
+
* declared as `({ schema })` — the normal shape for the component renderers —
|
|
21
|
+
* therefore never sees it.
|
|
22
|
+
*
|
|
23
|
+
* Measured through the real `SchemaRenderer` on `faac0d935`, one node per row,
|
|
24
|
+
* with a probe renderer that records both channels:
|
|
25
|
+
*
|
|
26
|
+
* { type: 'test:cap', props: { data: '${data.customers}' } }
|
|
27
|
+
* -> React prop `data` = the evaluated array, `schema.data` = undefined
|
|
28
|
+
* { type: 'test:cap', properties: { data: '${data.customers}' } }
|
|
29
|
+
* -> React prop `data` = the evaluated array, `schema.data` = the array
|
|
30
|
+
*
|
|
31
|
+
* Same key, same value, one envelope apart. The card's own four-leg reading of
|
|
32
|
+
* the same asymmetry through a real `data-table` is pinned in
|
|
33
|
+
* `packages/components/src/__tests__/data-table-node-data-diagnostic.test.tsx`
|
|
34
|
+
* (objectui#6665): the `props` leg renders `No results found`, the `properties`
|
|
35
|
+
* leg renders the rows.
|
|
36
|
+
*
|
|
37
|
+
* Every gate accepts the `props` spelling — `BaseSchema` is `.passthrough()`
|
|
38
|
+
* with `[key: string]: any` — and `props` is documented as the annotated legacy
|
|
39
|
+
* alias of the config bag, so nothing between the author and the screen says a
|
|
40
|
+
* word. That is the success-receipt shape objectui#6575 and objectui#6665 exist
|
|
41
|
+
* to remove; this is the third instance, and the first at a tier that covers
|
|
42
|
+
* every renderer instead of one component.
|
|
43
|
+
*
|
|
44
|
+
* ## What it deliberately does NOT do
|
|
45
|
+
*
|
|
46
|
+
* It changes NO behaviour. Hoisting `props` to parity with `properties` was
|
|
47
|
+
* option 1 and the maintainer REFUSED it (ruling 2026-08-29, verbatim
|
|
48
|
+
* 「同意」 on option 2): hoisting would weld the legacy alias in as a permanent
|
|
49
|
+
* second spelling, the opposite of this repo's alias-retirement direction.
|
|
50
|
+
* Refusing the key at parse was option 3 and stays blocked on the
|
|
51
|
+
* `.passthrough()` ceiling (objectui#5155 / objectui#6269). So the trap stops
|
|
52
|
+
* being silent; it does not stop being a trap, and what every renderer receives
|
|
53
|
+
* is byte-for-byte what it received before — pinned directly, not asserted, in
|
|
54
|
+
* `SchemaRenderer.propsBagDiagnostic.test.tsx`.
|
|
55
|
+
*
|
|
56
|
+
* ## Why `console.warn`, when its neighbour in this directory uses `error`
|
|
57
|
+
*
|
|
58
|
+
* Two conventions cross here and the choice follows the SHAPE, not the tier.
|
|
59
|
+
* `unevaluatedExpression.ts` sits at this same tier and shouts with
|
|
60
|
+
* `console.error`, but its subject is different: a raw `${…}` placed VERBATIM
|
|
61
|
+
* in front of a user. Nothing is placed here — a value is dropped. That is
|
|
62
|
+
* exactly objectui#6575's and objectui#6665's subject ("you declared something
|
|
63
|
+
* and the renderer dropped it"), and both of those emit `console.warn`. The
|
|
64
|
+
* card names this the third instance of that shape, so it joins that family.
|
|
65
|
+
*
|
|
66
|
+
* ## Level and dedupe were chosen from a census, not from taste
|
|
67
|
+
*
|
|
68
|
+
* The ruling fixed the order: measure component-level `props` usage across the
|
|
69
|
+
* in-repo corpus FIRST, and tune level/dedupe so the diagnostic informs rather
|
|
70
|
+
* than floods. Measured on `faac0d935` by walking every JSON document, every
|
|
71
|
+
* `json` fence in every `.md`/`.mdx`, and every TypeScript object literal in
|
|
72
|
+
* the repo (TS compiler API) for nodes carrying both `type` and `props`:
|
|
73
|
+
* 39 such nodes, of which 22 are on component-renderer types — and 19 of those
|
|
74
|
+
* 22 are test fixtures exercising this exact shape on purpose. The authored,
|
|
75
|
+
* non-test corpus holds 5, none of them in application runtime metadata.
|
|
76
|
+
*
|
|
77
|
+
* So there is nothing to flood, and the level is not softened for volume. The
|
|
78
|
+
* dedupe is still keyed on the MESSAGE rather than on the schema object,
|
|
79
|
+
* because the failure the census makes plausible is a metadata GENERATOR that
|
|
80
|
+
* emits the same wrong envelope on many nodes: those are distinct schema
|
|
81
|
+
* objects, so an object-keyed `WeakSet` (the shape
|
|
82
|
+
* `reportUnevaluatedExpressions` uses next door) would print one line per node
|
|
83
|
+
* for one authoring bug. Keying on the rendered message collapses that to one
|
|
84
|
+
* line while still giving two genuinely different nodes two lines. Full census
|
|
85
|
+
* table: the objectui#6708 PR body.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* The namespace whose renderers merge BOTH bags.
|
|
89
|
+
*
|
|
90
|
+
* Every `readProps()` in this repo that merges `{ ...schema.props,
|
|
91
|
+
* ...schema.properties }` belongs to a component registered with
|
|
92
|
+
* `namespace: 'element'` — measured by reading all five of them on
|
|
93
|
+
* `faac0d935`: `elements.tsx` (`element:text` / `divider` / `image` / `button`
|
|
94
|
+
* / `number`), `data-list.tsx` (`definition-list` / `repeater`),
|
|
95
|
+
* `text-input.tsx`, `record-picker.tsx` and `metadata-viewer.tsx`. For that
|
|
96
|
+
* family `props` is a legitimate spelling, so silence there is correct rather
|
|
97
|
+
* than a gap — and it is the direction the ruling pins explicitly.
|
|
98
|
+
*/
|
|
99
|
+
const ELEMENT_NAMESPACE_PREFIX = 'element:';
|
|
100
|
+
/**
|
|
101
|
+
* Node types OUTSIDE the `element:` namespace that nevertheless read the raw
|
|
102
|
+
* `props` bag off the schema, so the diagnostic must not claim their keys were
|
|
103
|
+
* dropped.
|
|
104
|
+
*
|
|
105
|
+
* Derived, not guessed: a repo-wide grep for reads of `schema.props` /
|
|
106
|
+
* `schema?.props` on `faac0d935` returns the `element:` family above plus
|
|
107
|
+
* exactly these. `plugin-view`'s `SimpleViewRenderer` reads
|
|
108
|
+
* `schema.props?.columns` for its grid layout.
|
|
109
|
+
*
|
|
110
|
+
* ⚠️ This list is a MEASUREMENT of the current tree, and its cost is stated
|
|
111
|
+
* rather than hidden: `view:simple` reads exactly one key out of the bag, so a
|
|
112
|
+
* `props` key other than `columns` on a `view:simple` node IS dropped and is
|
|
113
|
+
* NOT diagnosed. Silence on a node where the envelope is partly legitimate was
|
|
114
|
+
* preferred to a message that asserts a drop it did not check — a diagnostic
|
|
115
|
+
* that overstates its own consequence teaches authors to distrust it
|
|
116
|
+
* (objectui#6665's `describeIgnoredBind` makes the same trade). The honest way
|
|
117
|
+
* to shrink this list is to stop reading the legacy bag in that renderer, which
|
|
118
|
+
* is a behaviour change on a published component and not this card's to make.
|
|
119
|
+
*/
|
|
120
|
+
const NON_ELEMENT_PROPS_BAG_READERS = new Set(['view:simple']);
|
|
121
|
+
/**
|
|
122
|
+
* Does this node's renderer read the `props` bag as a config bag?
|
|
123
|
+
*
|
|
124
|
+
* Exported so the report and its pins cannot drift apart: one predicate, two
|
|
125
|
+
* readers. A non-string `type` answers `false` — an untyped node cannot be in
|
|
126
|
+
* a family — and the caller has already resolved a component for it, so the
|
|
127
|
+
* unknown-type box is not in play.
|
|
128
|
+
*/
|
|
129
|
+
export function readsPropsBag(type) {
|
|
130
|
+
if (typeof type !== 'string' || type.length === 0)
|
|
131
|
+
return false;
|
|
132
|
+
if (type.startsWith(ELEMENT_NAMESPACE_PREFIX))
|
|
133
|
+
return true;
|
|
134
|
+
return NON_ELEMENT_PROPS_BAG_READERS.has(type);
|
|
135
|
+
}
|
|
136
|
+
/** Prefix every line starts with — the handle tests, greps and log filters hold. */
|
|
137
|
+
export const DROPPED_PROPS_BAG_PREFIX = '[ObjectUI] A `props` config bag was not read';
|
|
138
|
+
/** Where the offending node lives, for the first line of the message. */
|
|
139
|
+
function describeAddress(type, id) {
|
|
140
|
+
const node = typeof type === 'string' && type ? `\`${type}\`` : '(untyped node)';
|
|
141
|
+
const where = typeof id === 'string' && id ? ` (id: '${id}')` : '';
|
|
142
|
+
return `${node}${where}`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Build the message. Separate from the emit so a test can assert the words a
|
|
146
|
+
* developer is going to read, not merely that something was logged.
|
|
147
|
+
*/
|
|
148
|
+
export function formatDroppedPropsBagMessage(type, id, droppedKeys) {
|
|
149
|
+
const keyList = droppedKeys.map(k => `\`${k}\``).join(', ');
|
|
150
|
+
const one = droppedKeys.length === 1;
|
|
151
|
+
return (`${DROPPED_PROPS_BAG_PREFIX} - node ${describeAddress(type, id)}\n` +
|
|
152
|
+
` ${one ? 'Key' : 'Keys'} under \`props\`: ${keyList}\n` +
|
|
153
|
+
'`props` is NOT hoisted onto the node — only `properties.*` is. It is spread as\n' +
|
|
154
|
+
`React props on the created element, so \`schema.${droppedKeys[0]}\` is undefined and a\n` +
|
|
155
|
+
`renderer declared as \`({ schema })\` never sees ${one ? 'this key' : 'these keys'}. Nothing throws and\n` +
|
|
156
|
+
'nothing is logged by the renderer: the node renders as if the bag were empty.\n' +
|
|
157
|
+
` Write ${one ? 'it' : 'them'} under \`properties\` instead (or at node level, where the node's\n` +
|
|
158
|
+
'schema declares the key). The `element:*` renderers merge both bags via\n' +
|
|
159
|
+
'`readProps()`; every other renderer reads `schema`. (objectui#6708)');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Reported messages, so a re-render — or a second node carrying the same
|
|
163
|
+
* authoring bug — does not repeat the line. Module state, exactly like
|
|
164
|
+
* `visibilityDiagnostic.ts`'s `Set`, and reset the same way for tests.
|
|
165
|
+
*/
|
|
166
|
+
const _warnedPropsBags = new Set();
|
|
167
|
+
/**
|
|
168
|
+
* Test-only reset for the dedupe above. Without it the second test to assert
|
|
169
|
+
* the same warning reads the first test's dedupe entry and sees silence — a
|
|
170
|
+
* green run that checked nothing.
|
|
171
|
+
*/
|
|
172
|
+
export function __resetDroppedPropsBagWarnings() {
|
|
173
|
+
_warnedPropsBags.clear();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Which keys of the OUTGOING props bag are dropped by a `schema`-reading
|
|
177
|
+
* renderer, or `null` when there is nothing to say.
|
|
178
|
+
*
|
|
179
|
+
* Two bags are read, and needing both is a MEASUREMENT rather than caution.
|
|
180
|
+
*
|
|
181
|
+
* `outgoingPropsBag` is the bag `SchemaRenderer` actually spreads — the value
|
|
182
|
+
* of `propsWithoutCanonicalKeys(schema.props, schema.properties)`, passed in
|
|
183
|
+
* rather than re-derived. That is what makes the message honest about the two
|
|
184
|
+
* cases it must not confuse:
|
|
185
|
+
*
|
|
186
|
+
* - a key BOTH bags declare has already been subtracted by that function
|
|
187
|
+
* (objectui#5123: `properties` wins on both channels), so the author is
|
|
188
|
+
* getting the canonical answer and nothing was silently dropped;
|
|
189
|
+
* - a key only `props` declares survives into that bag, and it is exactly the
|
|
190
|
+
* key that reaches no `schema` reader.
|
|
191
|
+
*
|
|
192
|
+
* `authoredPropsBag` is what the AUTHOR wrote, read off the original schema
|
|
193
|
+
* before the evaluation memo touched it. It is needed because that memo
|
|
194
|
+
* rebuilds the bag with `{ ...newSchema.props }` under a bare truthiness guard,
|
|
195
|
+
* so a degenerate `props: 'not-a-bag'` arrives at the spread site as
|
|
196
|
+
* `{ '0': 'n', '1': 'o', … }` — measured, not supposed. Reading only the
|
|
197
|
+
* outgoing bag would therefore report nine dropped keys named `0` … `8` and
|
|
198
|
+
* tell the author that `schema.0` is undefined, which is true and useless. A
|
|
199
|
+
* string is not a config bag; there is nothing to point at, and the shape-level
|
|
200
|
+
* defect it represents is a different question from this one.
|
|
201
|
+
*
|
|
202
|
+
* Deliberately narrow, and each exclusion is a reading rather than an omission:
|
|
203
|
+
*
|
|
204
|
+
* - an EMPTY bag says nothing was authored and nothing was lost;
|
|
205
|
+
* - a non-object authored `props` is the degenerate case above;
|
|
206
|
+
* - a node in the {@link readsPropsBag} family is silent by the ruling.
|
|
207
|
+
*/
|
|
208
|
+
export function collectDroppedPropsKeys(type, authoredPropsBag, outgoingPropsBag) {
|
|
209
|
+
if (readsPropsBag(type))
|
|
210
|
+
return null;
|
|
211
|
+
if (!isConfigBag(authoredPropsBag))
|
|
212
|
+
return null;
|
|
213
|
+
if (!isConfigBag(outgoingPropsBag))
|
|
214
|
+
return null;
|
|
215
|
+
const keys = Object.keys(outgoingPropsBag);
|
|
216
|
+
return keys.length > 0 ? keys : null;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Dev-build only. Reports once per distinct message via `console.warn`, and
|
|
220
|
+
* returns the message it emitted (or `null`) so a caller or a test can read the
|
|
221
|
+
* decision rather than infer it from a spy.
|
|
222
|
+
*
|
|
223
|
+
* The caller applies the production gate, so this stays a single dev-only
|
|
224
|
+
* branch at the call site and the whole module is dead code in a production
|
|
225
|
+
* build.
|
|
226
|
+
*/
|
|
227
|
+
export function reportDroppedPropsBag(type, id, authoredPropsBag, outgoingPropsBag) {
|
|
228
|
+
const droppedKeys = collectDroppedPropsKeys(type, authoredPropsBag, outgoingPropsBag);
|
|
229
|
+
if (!droppedKeys)
|
|
230
|
+
return null;
|
|
231
|
+
const message = formatDroppedPropsBagMessage(type, id, droppedKeys);
|
|
232
|
+
if (_warnedPropsBags.has(message))
|
|
233
|
+
return null;
|
|
234
|
+
_warnedPropsBags.add(message);
|
|
235
|
+
console.warn(message);
|
|
236
|
+
return message;
|
|
237
|
+
}
|
|
238
|
+
/* -------------------------------------------------------------------------- *
|
|
239
|
+
* objectui#9108 - the NODE-GATE half of the same bag
|
|
240
|
+
* -------------------------------------------------------------------------- */
|
|
241
|
+
/**
|
|
242
|
+
* Refusal: a NODE-GATE PREDICATE was parked under `props` (objectui#9108,
|
|
243
|
+
* maintainer ruling 2026-09-13, verbatim 「同意」 on the `domain:spec` seat's
|
|
244
|
+
* recommendation - REFUSE).
|
|
245
|
+
*
|
|
246
|
+
* ## The defect this refuses
|
|
247
|
+
*
|
|
248
|
+
* Measured at node level on `1e0e46af9`, four rows, both spellings and both
|
|
249
|
+
* polarities:
|
|
250
|
+
*
|
|
251
|
+
* { type: 'card', props: { visible: false } } -> the node RENDERED
|
|
252
|
+
* { type: 'card', props: { hidden: true } } -> the node RENDERED
|
|
253
|
+
* { type: 'card', properties: { visible: false } } -> correctly hidden
|
|
254
|
+
* { type: 'card', properties: { hidden: true } } -> correctly hidden
|
|
255
|
+
*
|
|
256
|
+
* `SchemaRenderer` hoists `properties.*` onto the node; NOTHING copies
|
|
257
|
+
* `props.*`. Both node gates read the post-hoist node, so a predicate that
|
|
258
|
+
* arrived under the alias is never one of the keys either gate can see. It is
|
|
259
|
+
* fail-OPEN and silent by construction - a gate that never bit renders exactly
|
|
260
|
+
* like a gate that said yes - so no user, no screenshot and no snapshot can
|
|
261
|
+
* find it. Only counting can.
|
|
262
|
+
*
|
|
263
|
+
* ## Why REFUSE and not HONOUR - this is a ruling, not a preference
|
|
264
|
+
*
|
|
265
|
+
* The opposite arm was implemented and CLOSED (PR objectui#9144). The ruling's
|
|
266
|
+
* decisive axis: honouring the alias would have made
|
|
267
|
+
* *"8 predicate keys work while the rest stayed silently dropped - and partly
|
|
268
|
+
* working is harder to learn from than not working"*, because the author
|
|
269
|
+
* generalises "`props` is fine" and collides again on the next key with the
|
|
270
|
+
* cause now further away. The three measured readings behind it, each with a
|
|
271
|
+
* control that fires: `@objectstack/spec` already refuses `props` by name
|
|
272
|
+
* (`unrecognized_keys ["props"]`); `skills/objectui/rules/protocol.md` teaches
|
|
273
|
+
* it as an ERROR rather than as a gap; and the producer census found ZERO
|
|
274
|
+
* authored `props` bags across `examples/**` and `content/docs/**` (controls:
|
|
275
|
+
* `visibleWhen` 9 files, `type` 488 files), so there is no capability to cut.
|
|
276
|
+
*
|
|
277
|
+
* ## What it deliberately does NOT do
|
|
278
|
+
*
|
|
279
|
+
* It changes NO verdict and NO rendered byte. Every gate answers exactly what
|
|
280
|
+
* it answered before, nothing is hoisted, `schema.<KEY>` stays undefined for a
|
|
281
|
+
* renderer declared as `({ schema })`, and the bag reaches the element with the
|
|
282
|
+
* same keys it always did. The trap stops being SILENT; it does not stop being
|
|
283
|
+
* a trap, which is the same posture objectui#6708 took one card earlier and the
|
|
284
|
+
* only posture a diagnostic can take without becoming the honour arm the ruling
|
|
285
|
+
* refused.
|
|
286
|
+
*
|
|
287
|
+
* ## Why `console.error`, and why NOT dev-only
|
|
288
|
+
*
|
|
289
|
+
* Two established postures in this tree, and this leg needs a leg of each:
|
|
290
|
+
*
|
|
291
|
+
* - SEVERITY from `unevaluatedExpression.ts`, which is this repo's REFUSAL
|
|
292
|
+
* tier. Its neighbour {@link reportDroppedPropsBag} warns because it
|
|
293
|
+
* reports a value that was dropped; the ruling's word here is *refused*,
|
|
294
|
+
* and the sibling gate diagnostic states the split explicitly ("not the
|
|
295
|
+
* refusal `reportUnevaluatedExpressions` emits").
|
|
296
|
+
* - ALWAYS-ON from `visibilityDiagnostic.ts`'s unresolvable-predicate leg,
|
|
297
|
+
* which the maintainer took out of `__DEV__` in objectui#6038 (ruling
|
|
298
|
+
* 2026-08-25, option B: "the silence is no longer an accepted property")
|
|
299
|
+
* on the ground that a node gate which has stopped biting in production is
|
|
300
|
+
* a class-1 defect that must not be able to sit live and undiscovered.
|
|
301
|
+
* This is that class in its widest form - the gate never bit at all, on
|
|
302
|
+
* every render - and the ruling's own confidence gap is about exactly the
|
|
303
|
+
* population a `__DEV__` gate would silence: *"The producer census covers
|
|
304
|
+
* this repository's `examples/**`, `content/docs/**` and skills corpus.
|
|
305
|
+
* It does NOT measure authored metadata in production. If stored documents
|
|
306
|
+
* out there do carry `props`, those authors are already suffering the
|
|
307
|
+
* silent drop today."*
|
|
308
|
+
*
|
|
309
|
+
* The rate limit that makes an always-on console line affordable is the same
|
|
310
|
+
* one objectui#6038 required: the dedupe below is keyed on the MESSAGE, so the
|
|
311
|
+
* ceiling is one line per distinct authoring bug for the lifetime of the page,
|
|
312
|
+
* not one line per render and not one per node.
|
|
313
|
+
*/
|
|
314
|
+
export const REFUSED_PROPS_PREDICATE_PREFIX = '[ObjectUI] A node-gate predicate under `props` is REFUSED';
|
|
315
|
+
/**
|
|
316
|
+
* Node-gate predicate keys the BAG-READING family can still honour itself, so
|
|
317
|
+
* refusing them there would cry wolf.
|
|
318
|
+
*
|
|
319
|
+
* Measured on this tree, not guessed: across every renderer that reads a config
|
|
320
|
+
* bag ({@link readsPropsBag} - the five `element:*` `readProps()` sites plus
|
|
321
|
+
* `view:simple`), the only node-gate predicate key any of them reads out of the
|
|
322
|
+
* bag is `disabled`, in `elements.tsx` (`element:button`:
|
|
323
|
+
* `disabled={props.disabled || running}`) and `text-input.tsx`
|
|
324
|
+
* (`element:text-input`). No visibility-chain key is read from a bag by any
|
|
325
|
+
* renderer in this repo, and neither is `disabledOn`.
|
|
326
|
+
*
|
|
327
|
+
* So `{ type: 'element:button', props: { disabled: true } }` really does
|
|
328
|
+
* produce a disabled button - the node GATE did not bite, but the author got
|
|
329
|
+
* the effect they asked for, and a refusal there would send them looking for a
|
|
330
|
+
* defect that is not on their screen. Every other row stays refused on every
|
|
331
|
+
* family, including the `element:*` visibility rows, which nothing honours.
|
|
332
|
+
*/
|
|
333
|
+
const BAG_HONOURED_GATE_KEYS = new Set(['disabled']);
|
|
334
|
+
/**
|
|
335
|
+
* Build the refusal message. Separate from the emit so a test can assert the
|
|
336
|
+
* words a developer is going to read, not merely that something was logged.
|
|
337
|
+
*
|
|
338
|
+
* Carries the MIGRATION LINE the ruling requires by name - *"the implementing
|
|
339
|
+
* round should pair the refusal with a named migration line in the diagnostic,
|
|
340
|
+
* not let half the keys quietly start working"*. It names the keys it refused
|
|
341
|
+
* and the two spellings that do work, and it names nothing else: there is no
|
|
342
|
+
* per-key canonical-alias table here, because inventing one would be a second
|
|
343
|
+
* declaration of ADR-0089's normalization competing with the spec's own.
|
|
344
|
+
*/
|
|
345
|
+
export function formatRefusedPropsPredicateMessage(type, id, refusedKeys) {
|
|
346
|
+
const keyList = refusedKeys.map(k => `\`${k}\``).join(', ');
|
|
347
|
+
const one = refusedKeys.length === 1;
|
|
348
|
+
return (`${REFUSED_PROPS_PREDICATE_PREFIX} - node ${describeAddress(type, id)}\n` +
|
|
349
|
+
` ${one ? 'Key' : 'Keys'} parked under \`props\`: ${keyList}\n` +
|
|
350
|
+
'`props` is NOT an authoring surface for the node gates. `SchemaRenderer`\n' +
|
|
351
|
+
'hoists `properties.*` onto the node and reads its visibility / enablement\n' +
|
|
352
|
+
'gates from there; nothing copies `props.*`, so this predicate is never a key\n' +
|
|
353
|
+
`either gate can see. ${one ? 'It gates' : 'They gate'} nothing - the node renders and stays enabled\n` +
|
|
354
|
+
'exactly as if the predicate said yes, which is why nothing on screen says so.\n' +
|
|
355
|
+
` MIGRATION: move ${one ? 'it' : 'them'} out of \`props\` - onto the node itself\n` +
|
|
356
|
+
'(`{ "type": "card", "visibleWhen": ... }`, the spelling the spec declares), or\n' +
|
|
357
|
+
'into the `properties` bag, which IS hoisted. The worked pair is in\n' +
|
|
358
|
+
'`skills/objectui/rules/protocol.md`. (objectui#9108)');
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Which node-gate predicate keys of the PARKED `props` bag are refused, or
|
|
362
|
+
* `null` when there is nothing to say.
|
|
363
|
+
*
|
|
364
|
+
* `nodeGateKeys` is passed IN rather than declared here: `SchemaRenderer` owns
|
|
365
|
+
* the two chain declarations and derives this union from them, so a leg added
|
|
366
|
+
* to either chain is covered by the same edit that adds it and this module
|
|
367
|
+
* never grows a twin list to drift from them (AGENTS.md #9).
|
|
368
|
+
*
|
|
369
|
+
* `parkedPropsBag` is the value of
|
|
370
|
+
* `propsWithoutCanonicalKeys(schema.props, schema.properties)` - the same
|
|
371
|
+
* subtraction the outgoing bag and the objectui#6708 diagnostic already use, so
|
|
372
|
+
* the two cases stay un-confused exactly as they are there: a key BOTH bags
|
|
373
|
+
* declare has already been subtracted (objectui#5123: `properties` wins, the
|
|
374
|
+
* author is getting the canonical answer and nothing is parked), while a key
|
|
375
|
+
* only `props` declares survives into that bag and is precisely the one no gate
|
|
376
|
+
* can see. A degenerate `props` contributes no keys through that same function,
|
|
377
|
+
* so `props: 'not-a-bag'` refuses nothing here - its defect is a different
|
|
378
|
+
* question (objectui#6752).
|
|
379
|
+
*/
|
|
380
|
+
export function collectRefusedPropsPredicateKeys(type, nodeGateKeys, parkedPropsBag) {
|
|
381
|
+
if (!isConfigBag(parkedPropsBag))
|
|
382
|
+
return null;
|
|
383
|
+
const bagReader = readsPropsBag(type);
|
|
384
|
+
const refused = Object.keys(parkedPropsBag).filter(key => nodeGateKeys.has(key) && !(bagReader && BAG_HONOURED_GATE_KEYS.has(key)));
|
|
385
|
+
return refused.length > 0 ? refused : null;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Reported messages, so a re-render - or a second node carrying the same
|
|
389
|
+
* authoring bug - does not repeat the line. Module state, exactly like
|
|
390
|
+
* {@link reportDroppedPropsBag}'s `Set` next door, and reset the same way for
|
|
391
|
+
* tests.
|
|
392
|
+
*/
|
|
393
|
+
const _refusedPropsPredicates = new Set();
|
|
394
|
+
/**
|
|
395
|
+
* Test-only reset for the dedupe above. Without it the second test to assert
|
|
396
|
+
* the same refusal reads the first test's dedupe entry and sees silence - a
|
|
397
|
+
* green run that checked nothing.
|
|
398
|
+
*/
|
|
399
|
+
export function __resetRefusedPropsPredicateWarnings() {
|
|
400
|
+
_refusedPropsPredicates.clear();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Reports a node-gate predicate parked under `props`, in DEVELOPMENT AND IN
|
|
404
|
+
* PRODUCTION - read {@link REFUSED_PROPS_PREDICATE_PREFIX}'s docblock for why
|
|
405
|
+
* this leg carries neither the `__DEV__` gate nor the `console.warn` severity
|
|
406
|
+
* of its neighbour in this module.
|
|
407
|
+
*
|
|
408
|
+
* Returns the message it emitted (or `null`) so a caller or a test can read the
|
|
409
|
+
* decision rather than infer it from a spy.
|
|
410
|
+
*/
|
|
411
|
+
export function reportRefusedPropsPredicate(type, id, nodeGateKeys, parkedPropsBag) {
|
|
412
|
+
const refusedKeys = collectRefusedPropsPredicateKeys(type, nodeGateKeys, parkedPropsBag);
|
|
413
|
+
if (!refusedKeys)
|
|
414
|
+
return null;
|
|
415
|
+
const message = formatRefusedPropsPredicateMessage(type, id, refusedKeys);
|
|
416
|
+
if (_refusedPropsPredicates.has(message))
|
|
417
|
+
return null;
|
|
418
|
+
_refusedPropsPredicates.add(message);
|
|
419
|
+
console.error(message);
|
|
420
|
+
return message;
|
|
421
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
* Test-only reset for the dedupe above. Without it the second test to assert
|
|
10
|
+
* the same warning reads the first test's dedupe entry and sees silence — a
|
|
11
|
+
* green run that checked nothing.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ The dangerous shape is the ABSENCE assertion, and it is why this export is
|
|
14
|
+
* a correctness affordance rather than a convenience: a test that asserts this
|
|
15
|
+
* warning did NOT fire for a `type#id` an earlier test already warned for is
|
|
16
|
+
* green whatever the renderer does, and it reports that green as coverage
|
|
17
|
+
* (objectui#9959). `SchemaRenderer.refusedDataPropWarnReset-9959.test.tsx`
|
|
18
|
+
* renders one `type#id` across separate `it()` blocks and goes red when this
|
|
19
|
+
* body stops clearing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function __resetRefusedDataPropWarnings(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Say why an authored `data` key did not reach the block (objectui#9571,
|
|
24
|
+
* ruling objectui#8348 Q2-C, decision batch #136 item 3, maintainer 「同意」).
|
|
25
|
+
*
|
|
26
|
+
* ## Why this diagnostic is part of the change and not decoration
|
|
27
|
+
*
|
|
28
|
+
* ⛔ MEASURED, and it corrects the card's own premise: the ladder emits NO
|
|
29
|
+
* runtime signal when it refuses an off-arm `data`. `resolveRecordSourceConfig`
|
|
30
|
+
* returns `null` and falls through silently, and `validateSchema` — the
|
|
31
|
+
* `__DEV__` pass in `SchemaRenderer.tsx` — never reads `data` against the
|
|
32
|
+
* block's row at all. The refusal is loud at AUTHORING time (`os validate`, the
|
|
33
|
+
* save gate and the zod row all reject a bare array) and mute at render time.
|
|
34
|
+
*
|
|
35
|
+
* So without this line, retiring the props carrier is exactly the failure shape
|
|
36
|
+
* AGENTS.md #0.1 and `ObjectGrid`'s own column diagnostic exist to prevent:
|
|
37
|
+
* renderer and author disagree, and the author gets a success receipt — a page
|
|
38
|
+
* whose rows were on screen yesterday is blank today, with nothing anywhere
|
|
39
|
+
* naming the key that was dropped or the spelling that would work.
|
|
40
|
+
*
|
|
41
|
+
* Read-only and `__DEV__`-only: it reports the decision made at the call site
|
|
42
|
+
* and changes nothing about what is rendered.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reportRefusedDataPropSpread(type: string, id: string | undefined): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
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: an authored `data` key that did NOT reach the block as
|
|
10
|
+
* a React prop, because that block's published `data` row is the `ViewData`
|
|
11
|
+
* OBJECT arm (objectui#9571, extended to the legacy `props` alias bag by
|
|
12
|
+
* objectui#9758).
|
|
13
|
+
*
|
|
14
|
+
* ## Why the diagnostic lives HERE and not in `SchemaRenderer.tsx` (objectui#9959)
|
|
15
|
+
*
|
|
16
|
+
* It was born as module state inside `SchemaRenderer.tsx`, next to the two call
|
|
17
|
+
* sites that emit it. Every other dev diagnostic in this directory —
|
|
18
|
+
* `propsBagDiagnostic.ts`'s two and `visibilityDiagnostic.ts`'s one — pairs its
|
|
19
|
+
* dedupe `Set` with an exported test-only reset, and each of those resets says
|
|
20
|
+
* in its own docblock what it exists to prevent. This one had no reset, and it
|
|
21
|
+
* could not simply grow one where it stood: `index.ts` re-exports
|
|
22
|
+
* `SchemaRenderer.js` with a star, so a `__reset` added there would have been a
|
|
23
|
+
* new member of this package's PUBLISHED surface — a contract change, for a
|
|
24
|
+
* test affordance. Moving the `Set`, the emit and the reset into this module
|
|
25
|
+
* puts all three where their siblings already live and adds nothing to the
|
|
26
|
+
* published entry, which is the whole shape of the remedy.
|
|
27
|
+
*
|
|
28
|
+
* ## The caller owns the production gate
|
|
29
|
+
*
|
|
30
|
+
* Same contract as `reportDroppedPropsBag` next door: `SchemaRenderer` applies
|
|
31
|
+
* `__DEV__` at the call site, so this stays one dev-only branch there and the
|
|
32
|
+
* whole module is dead code in a production build.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Warn ONCE per distinct refused node, not once per render (objectui#9571).
|
|
36
|
+
*
|
|
37
|
+
* The strip at the call site runs on every render of the node, and a warning
|
|
38
|
+
* that floods the console is a warning that gets muted — the same warn-once
|
|
39
|
+
* discipline `ObjectMap`'s legacy-config notice and the visibility-predicate
|
|
40
|
+
* diagnostics already use. Keyed on the node's type plus its id, which is the
|
|
41
|
+
* pair an author can act on; a node with no `id` is keyed on its type alone and
|
|
42
|
+
* so warns once per type, which is the honest ceiling for something that cannot
|
|
43
|
+
* be told apart.
|
|
44
|
+
*/
|
|
45
|
+
const _warnedRefusedDataPropNodes = new Set();
|
|
46
|
+
/**
|
|
47
|
+
* Test-only reset for the dedupe above. Without it the second test to assert
|
|
48
|
+
* the same warning reads the first test's dedupe entry and sees silence — a
|
|
49
|
+
* green run that checked nothing.
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ The dangerous shape is the ABSENCE assertion, and it is why this export is
|
|
52
|
+
* a correctness affordance rather than a convenience: a test that asserts this
|
|
53
|
+
* warning did NOT fire for a `type#id` an earlier test already warned for is
|
|
54
|
+
* green whatever the renderer does, and it reports that green as coverage
|
|
55
|
+
* (objectui#9959). `SchemaRenderer.refusedDataPropWarnReset-9959.test.tsx`
|
|
56
|
+
* renders one `type#id` across separate `it()` blocks and goes red when this
|
|
57
|
+
* body stops clearing.
|
|
58
|
+
*/
|
|
59
|
+
export function __resetRefusedDataPropWarnings() {
|
|
60
|
+
_warnedRefusedDataPropNodes.clear();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Say why an authored `data` key did not reach the block (objectui#9571,
|
|
64
|
+
* ruling objectui#8348 Q2-C, decision batch #136 item 3, maintainer 「同意」).
|
|
65
|
+
*
|
|
66
|
+
* ## Why this diagnostic is part of the change and not decoration
|
|
67
|
+
*
|
|
68
|
+
* ⛔ MEASURED, and it corrects the card's own premise: the ladder emits NO
|
|
69
|
+
* runtime signal when it refuses an off-arm `data`. `resolveRecordSourceConfig`
|
|
70
|
+
* returns `null` and falls through silently, and `validateSchema` — the
|
|
71
|
+
* `__DEV__` pass in `SchemaRenderer.tsx` — never reads `data` against the
|
|
72
|
+
* block's row at all. The refusal is loud at AUTHORING time (`os validate`, the
|
|
73
|
+
* save gate and the zod row all reject a bare array) and mute at render time.
|
|
74
|
+
*
|
|
75
|
+
* So without this line, retiring the props carrier is exactly the failure shape
|
|
76
|
+
* AGENTS.md #0.1 and `ObjectGrid`'s own column diagnostic exist to prevent:
|
|
77
|
+
* renderer and author disagree, and the author gets a success receipt — a page
|
|
78
|
+
* whose rows were on screen yesterday is blank today, with nothing anywhere
|
|
79
|
+
* naming the key that was dropped or the spelling that would work.
|
|
80
|
+
*
|
|
81
|
+
* Read-only and `__DEV__`-only: it reports the decision made at the call site
|
|
82
|
+
* and changes nothing about what is rendered.
|
|
83
|
+
*/
|
|
84
|
+
export function reportRefusedDataPropSpread(type, id) {
|
|
85
|
+
const key = id === undefined ? type : `${type}#${id}`;
|
|
86
|
+
if (_warnedRefusedDataPropNodes.has(key))
|
|
87
|
+
return;
|
|
88
|
+
_warnedRefusedDataPropNodes.add(key);
|
|
89
|
+
console.warn(`[ObjectUI] SchemaRenderer: the authored \`data\` key on <${type}${id === undefined ? '' : ` id="${id}"`}> was NOT passed to the component as a React prop. This block's published ` +
|
|
90
|
+
'`data` row is the `ViewData` OBJECT arm, so `data` is read from the schema ' +
|
|
91
|
+
'and judged by that row (objectui#8348). Inline rows go at ' +
|
|
92
|
+
'`data: { provider: "value", items: [...] }`; a bare array under `data` is ' +
|
|
93
|
+
'refused. A HOST passing rows down as a React `data` prop is unaffected.');
|
|
94
|
+
}
|
|
@@ -16,7 +16,10 @@ export declare function findExpressionSources(value: string): string[];
|
|
|
16
16
|
/** Which authored channel a residual expression was found on. */
|
|
17
17
|
export type UnevaluatedChannel = 'schema' | 'properties' | 'props';
|
|
18
18
|
export interface UnevaluatedExpressionFinding {
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The key holding the raw source — or, for a leaf of a `params` bag, that
|
|
21
|
+
* leaf's path from the bag (`params.target.id`, `params.ids[0]`).
|
|
22
|
+
*/
|
|
20
23
|
key: string;
|
|
21
24
|
/** The authored channel the key was read from. */
|
|
22
25
|
channel: UnevaluatedChannel;
|
|
@@ -51,6 +54,44 @@ export declare const UNEVALUATED_EXPRESSION_PREFIX = "[ObjectUI] Unevaluated exp
|
|
|
51
54
|
/**
|
|
52
55
|
* Build the message. Separate from the emit so a test can assert the words a
|
|
53
56
|
* developer is going to read, not merely that something was logged.
|
|
57
|
+
*
|
|
58
|
+
* ## Why `properties.*` is in the channel list (objectui#7849)
|
|
59
|
+
*
|
|
60
|
+
* It was missing, and the omission CONTRADICTED the sibling diagnostic in
|
|
61
|
+
* `propsBagDiagnostic.ts`, which tells an author who wrote the same key one
|
|
62
|
+
* envelope out: *"`props` is NOT hoisted onto the node — only `properties.*`
|
|
63
|
+
* is … Write them under `properties` instead."* An author whose node trips
|
|
64
|
+
* both messages was told to use `properties` by one and, by the other, that
|
|
65
|
+
* the only channels that work are `content` or host-side resolution.
|
|
66
|
+
*
|
|
67
|
+
* The enumeration was the wrong half. `properties` is not a channel on its way
|
|
68
|
+
* out and there is no retirement on record for it: `@objectstack/spec@17.2.0`
|
|
69
|
+
* calls the vocabulary carried there *"ALIVE — this is not dead surface to
|
|
70
|
+
* retire under ADR-0049"*, keeps `PageComponentSchema.properties` as the open
|
|
71
|
+
* carrier ON PURPOSE, gates it at the authoring door
|
|
72
|
+
* (`validate-component-props.ts`), and tombstones no part of it via
|
|
73
|
+
* `retiredKey()`. In THIS repo `props` — not `properties` — is the spelling
|
|
74
|
+
* annotated as the legacy alias (`SchemaRenderer.tsx`: "the legacy `props`
|
|
75
|
+
* bag, minus every key the canonical `properties` bag also …").
|
|
76
|
+
*
|
|
77
|
+
* Measured on this branch's base through the real renderers, not inferred:
|
|
78
|
+
* `badge` with `properties.label = '${data.status}'` renders `completed` and
|
|
79
|
+
* applies the variant classes; `card` with a conditional
|
|
80
|
+
* `properties.className` renders `class="… border-red-500"`; the same keys
|
|
81
|
+
* under `props` render an EMPTY badge body and no `border-red-500`.
|
|
82
|
+
*
|
|
83
|
+
* The words are borrowed from the sibling message on purpose — one hoist, one
|
|
84
|
+
* vocabulary, so the two diagnostics cannot drift apart again. Pinned by
|
|
85
|
+
* `__tests__/diagnosticChannelConsistency.test.ts`.
|
|
86
|
+
*
|
|
87
|
+
* ## Why `params` is in it too (objectui#7867)
|
|
88
|
+
*
|
|
89
|
+
* The same defect #7849 closed, one channel later: once every string leaf of a
|
|
90
|
+
* `params` bag evaluates, an enumeration that left `params` out would tell an
|
|
91
|
+
* author whose `params.recordId` template THREW that `params` is not a channel
|
|
92
|
+
* at all — and send them to move a correct key somewhere else. A finding on a
|
|
93
|
+
* `params` leaf means the second sentence above (the expression threw, or the
|
|
94
|
+
* value it produced still reads as a template), never the first.
|
|
54
95
|
*/
|
|
55
96
|
export declare function formatUnevaluatedExpressionMessage(type: unknown, id: unknown, findings: UnevaluatedExpressionFinding[]): string;
|
|
56
97
|
/**
|