@object-ui/core 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 +6610 -0
- package/README.md +101 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +288 -27
- package/dist/actions/ActionRunner.js +443 -63
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +57 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- 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/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- 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/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 +203 -31
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -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 +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +80 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +358 -31
- package/dist/registry/Registry.js +263 -11
- 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/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 +131 -5
- package/dist/utils/chart-series.js +98 -16
- 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 +354 -0
- package/dist/utils/date-display.js +463 -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 +76 -6
- package/dist/utils/expand-fields.js +76 -6
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- 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 +121 -1
- package/dist/utils/normalize-list-view.js +186 -6
- 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 +137 -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 +200 -82
- 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
|
@@ -0,0 +1,167 @@
|
|
|
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
|
+
* native-date-value - the ONE adapter set between a stored date / datetime
|
|
10
|
+
* value and the native date/time controls (objectui#3127).
|
|
11
|
+
*
|
|
12
|
+
* ## Why it lives in `core` (objectui#10625)
|
|
13
|
+
*
|
|
14
|
+
* It was written in `@object-ui/fields` (`widgets/nativeDateValue.ts`), and
|
|
15
|
+
* `@object-ui/components`' data table could not import it from there:
|
|
16
|
+
* `fields` depends on `components`, so the reverse import is a package cycle.
|
|
17
|
+
* The table therefore kept private copies with no impossible-day check, and a
|
|
18
|
+
* stored `2026-02-30T10:00:00Z` showed as March 2nd in its inline editor and
|
|
19
|
+
* could be written back as that day. The functions move DOWN into the
|
|
20
|
+
* React-free engine, next to the judgement they share (`isRealCalendarDate`
|
|
21
|
+
* in `date-display.ts`), and `fields` re-exports them unchanged: one home,
|
|
22
|
+
* nothing to drift. `date-display.ts`'s header is the same remedy applied to
|
|
23
|
+
* the display side.
|
|
24
|
+
*
|
|
25
|
+
* ## The defect these close
|
|
26
|
+
*
|
|
27
|
+
* `<input type="date">` accepts exactly `YYYY-MM-DD` and
|
|
28
|
+
* `<input type="datetime-local">` exactly `YYYY-MM-DDTHH:mm[:ss]` — a **local
|
|
29
|
+
* wall clock with no zone suffix**. Anything else is not "coerced" or
|
|
30
|
+
* "warned about": the browser SILENTLY rejects it. The attribute still lands
|
|
31
|
+
* in the DOM, so the value looks present to anyone inspecting markup, while
|
|
32
|
+
* `input.value` reads back `""` and the control paints its empty placeholder:
|
|
33
|
+
*
|
|
34
|
+
* ```js
|
|
35
|
+
* el.getAttribute('value') // "2026-08-16T00:33:00.000Z" ← the record's value
|
|
36
|
+
* el.value // "" ← what the user sees
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Records come back from the API as full ISO-8601 (`…T00:33:00.000Z`) — which
|
|
40
|
+
* is precisely one of those rejected shapes. So EVERY datetime field in an
|
|
41
|
+
* edit dialog rendered blank while holding a real value, and users read that
|
|
42
|
+
* as "the date was lost" and retyped it (which is the only way the display bug
|
|
43
|
+
* could turn into a data change).
|
|
44
|
+
*
|
|
45
|
+
* ## Why a round trip, and not just a formatter
|
|
46
|
+
*
|
|
47
|
+
* Formatting on the way IN without converting on the way OUT would be the
|
|
48
|
+
* worse bug: the control would then be read as UTC but written back as a naive
|
|
49
|
+
* local string, so a user in UTC+8 who picked "08:33" would store 08:33Z and
|
|
50
|
+
* see 16:33 on the detail page. Read basis and write basis must be the same
|
|
51
|
+
* one, so `toDateTimeInputValue` / `fromDateTimeInputValue` are a pair and are
|
|
52
|
+
* used as a pair.
|
|
53
|
+
*
|
|
54
|
+
* ## Shape kept in form state
|
|
55
|
+
*
|
|
56
|
+
* `datetime` round-trips through ISO — the shape the API hands us and the one
|
|
57
|
+
* display/format code already assumes. `date` stays a plain `YYYY-MM-DD`
|
|
58
|
+
* string. Untouched fields are never re-emitted, so a form that opens and
|
|
59
|
+
* closes without an edit still submits nothing for them.
|
|
60
|
+
*
|
|
61
|
+
* ## A stored day that does not exist (objectui#10474)
|
|
62
|
+
*
|
|
63
|
+
* The engine's date parse accepts a day of `01`-`31` for every month and rolls
|
|
64
|
+
* the surplus forward, so `new Date('2026-02-30T10:00:00Z')` is March 2nd. The
|
|
65
|
+
* READ faces refuse such a value through the shared judgement
|
|
66
|
+
* (`isRealCalendarDate`, objectui#10026 / objectui#10301); this pair used to
|
|
67
|
+
* reach the engine first, so the editor showed `2026-03-02T10:00` for it and a
|
|
68
|
+
* save could write that day back. `isImpossibleStoredDay` asks the same
|
|
69
|
+
* judgement of the day AS WRITTEN, before any `new Date(...)`, in every
|
|
70
|
+
* spelling (`Z`, offset, zone-less, date-only).
|
|
71
|
+
*
|
|
72
|
+
* The control cannot show the stored string instead: measured in Chromium, a
|
|
73
|
+
* `datetime-local` (and a `date`) sanitises a nonexistent day to `""`, in the
|
|
74
|
+
* zone-less spelling too. So `toDateTimeInputValue` hands the control `""` and
|
|
75
|
+
* the WIDGET names the stored string beside it with an invalid marker — an
|
|
76
|
+
* empty control alone would be objectui#3127's silent blank again. Nothing is
|
|
77
|
+
* written until the user picks a new value.
|
|
78
|
+
*/
|
|
79
|
+
import { isRealCalendarDate } from './date-display.js';
|
|
80
|
+
function pad2(n) {
|
|
81
|
+
return String(n).padStart(2, '0');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stored value → `<input type="date">` value (`YYYY-MM-DD`).
|
|
85
|
+
*
|
|
86
|
+
* A leading `YYYY-MM-DD` is passed through VERBATIM rather than re-parsed: a
|
|
87
|
+
* date-only string is parsed as UTC midnight by `new Date()`, so reading local
|
|
88
|
+
* calendar components back out of it shifts the day by one everywhere west of
|
|
89
|
+
* Greenwich. `2026-06-17` must stay June 17th in every zone.
|
|
90
|
+
*/
|
|
91
|
+
export function toDateInputValue(value) {
|
|
92
|
+
if (value == null || value === '')
|
|
93
|
+
return '';
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
const m = value.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
96
|
+
if (m)
|
|
97
|
+
return m[1];
|
|
98
|
+
}
|
|
99
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
100
|
+
if (Number.isNaN(d.getTime()))
|
|
101
|
+
return '';
|
|
102
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Stored value → `<input type="datetime-local">` value (`YYYY-MM-DDTHH:mm`),
|
|
106
|
+
* expressed in the viewer's local wall clock — the same instant the detail
|
|
107
|
+
* page shows, so opening the editor never appears to move the time.
|
|
108
|
+
*
|
|
109
|
+
* A value that is ALREADY a zone-less local datetime is truncated to minutes
|
|
110
|
+
* in place instead of being round-tripped through `Date`, which would
|
|
111
|
+
* reinterpret it and then re-render it, twice risking an off-by-a-zone shift.
|
|
112
|
+
*/
|
|
113
|
+
export function toDateTimeInputValue(value) {
|
|
114
|
+
if (value == null || value === '')
|
|
115
|
+
return '';
|
|
116
|
+
// Judged before any `new Date(...)`, which would roll it (objectui#10474).
|
|
117
|
+
if (isImpossibleStoredDay(value))
|
|
118
|
+
return '';
|
|
119
|
+
if (typeof value === 'string') {
|
|
120
|
+
const local = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::\d{2}(?:\.\d+)?)?$/);
|
|
121
|
+
if (local)
|
|
122
|
+
return `${local[1]}T${local[2]}`;
|
|
123
|
+
}
|
|
124
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
125
|
+
if (Number.isNaN(d.getTime()))
|
|
126
|
+
return '';
|
|
127
|
+
return (`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` +
|
|
128
|
+
`T${pad2(d.getHours())}:${pad2(d.getMinutes())}`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* `<input type="datetime-local">` value → stored value (ISO-8601, UTC).
|
|
132
|
+
*
|
|
133
|
+
* The control hands us a zone-less local wall clock; `new Date()` reads that
|
|
134
|
+
* form as local time, which is the inverse of what `toDateTimeInputValue`
|
|
135
|
+
* wrote — the pair is what keeps read and write on one basis.
|
|
136
|
+
*
|
|
137
|
+
* An unparseable string is returned untouched rather than blanked: dropping a
|
|
138
|
+
* value the user can see in the box would be a data loss dressed up as a fix.
|
|
139
|
+
*/
|
|
140
|
+
export function fromDateTimeInputValue(value) {
|
|
141
|
+
if (!value)
|
|
142
|
+
return '';
|
|
143
|
+
// A nonexistent day is returned untouched, like an unparseable string: the
|
|
144
|
+
// engine would re-emit it as the rolled day (objectui#10474).
|
|
145
|
+
if (isImpossibleStoredDay(value))
|
|
146
|
+
return value;
|
|
147
|
+
const d = new Date(value);
|
|
148
|
+
if (Number.isNaN(d.getTime()))
|
|
149
|
+
return value;
|
|
150
|
+
return d.toISOString();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* True when a stored value is written on a calendar day that does not exist
|
|
154
|
+
* (`2026-02-30`, `2026-02-30T10:00:00Z`, `2026-02-30T10:00+08:00`) — the day
|
|
155
|
+
* read AS WRITTEN from the leading `YYYY-MM-DD`, by the one judgement the read
|
|
156
|
+
* faces use (`isRealCalendarDate`). A parsed `Date` always names a real day,
|
|
157
|
+
* so the check can only be made before any conversion.
|
|
158
|
+
*
|
|
159
|
+
* Anything without a leading `YYYY-MM-DD` (a `Date`, a number, free text) is
|
|
160
|
+
* not this case and answers `false`.
|
|
161
|
+
*/
|
|
162
|
+
export function isImpossibleStoredDay(value) {
|
|
163
|
+
if (typeof value !== 'string')
|
|
164
|
+
return false;
|
|
165
|
+
const m = value.match(/^(\d{4}-\d{2}-\d{2})(?:$|T)/);
|
|
166
|
+
return m != null && !isRealCalendarDate(m[1]);
|
|
167
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
* The platform's hard row ceiling for a NON-GRID visualisation — gantt,
|
|
10
|
+
* calendar, map and tree (objectui#7210, maintainer ruling a′, 2026-09-02).
|
|
11
|
+
*
|
|
12
|
+
* ## Why this lives in `@object-ui/core` (objectui#7508, ruling A′)
|
|
13
|
+
*
|
|
14
|
+
* The four views live in four packages, so whatever they share is public by
|
|
15
|
+
* construction: every package here has a single entry and no subpath export.
|
|
16
|
+
* The non-React half of the mechanism — this constant, the query it sends and
|
|
17
|
+
* {@link applyNonGridRowCeiling} — wraps this package's own
|
|
18
|
+
* `extractRecords`, and every one of the four already imports core helpers,
|
|
19
|
+
* so this is its home. `@object-ui/react` re-exports the three names it
|
|
20
|
+
* carried before, and keeps only the React half (`NonGridRowCeilingNote`).
|
|
21
|
+
*
|
|
22
|
+
* ## What was ruled, and what was rejected
|
|
23
|
+
*
|
|
24
|
+
* A non-grid visualisation may fetch the whole FILTERED result set, because a
|
|
25
|
+
* truthful range or layout needs all of it: a gantt cannot compute
|
|
26
|
+
* `min(start) → max(end)` from one page, and a map cannot fit a camera to
|
|
27
|
+
* markers it never received. What it may not do is fetch it UNBOUNDED — at
|
|
28
|
+
* 100k scheduled rows that is the whole table into the browser, with no knob
|
|
29
|
+
* reachable from view metadata, since these four requests never carried a
|
|
30
|
+
* `$top` for `pagination.pageSize` to set.
|
|
31
|
+
*
|
|
32
|
+
* So the fetch carries a ceiling, and the ceiling is a NAMED CONSTANT IN THE
|
|
33
|
+
* RENDERER — not an authorable view key. Three alternatives were considered
|
|
34
|
+
* and are not what landed:
|
|
35
|
+
*
|
|
36
|
+
* - a documentation note only — still the whole table into the browser;
|
|
37
|
+
* - truncate at `pagination.pageSize` — SILENT truncation, and it caps a
|
|
38
|
+
* complete schedule at one page;
|
|
39
|
+
* - an authorable `maxRows` key — a new permanent key every author must set.
|
|
40
|
+
*
|
|
41
|
+
* ⛔ The direction the ruling names as dangerous is SILENT truncation: a
|
|
42
|
+
* cut-off schedule still looks like a schedule, a cut-off map still looks like
|
|
43
|
+
* a map. Nothing on screen distinguishes "these are all the records" from
|
|
44
|
+
* "these are the first 2,000 of 40,000" unless the view says so. That is why
|
|
45
|
+
* `NonGridRowCeilingNote` (`@object-ui/react`) is not decoration — crossing
|
|
46
|
+
* this ceiling quietly is the defect, and the note is the fix.
|
|
47
|
+
*
|
|
48
|
+
* ## Why 2,000, measured
|
|
49
|
+
*
|
|
50
|
+
* The ruling sets ONE constant across the four views, chosen after measuring
|
|
51
|
+
* them, so the binding view decides it. Measured in this repo's jsdom lane
|
|
52
|
+
* (`@testing-library/react`, real child views, inline `value` provider, mount
|
|
53
|
+
* to settled paint), DOM elements materialised and mount duration:
|
|
54
|
+
*
|
|
55
|
+
* | rows | gantt | calendar | map | tree |
|
|
56
|
+
* |------:|-----------:|----------:|---------------:|----------------:|
|
|
57
|
+
* | 250 | 726 · 314ms | 415 · 231ms | 478 · 152ms | 1,306 · 326ms |
|
|
58
|
+
* | 1,000 | 726 · 226ms | 415 · 235ms | 1,512 · 299ms | 5,206 · 1,025ms |
|
|
59
|
+
* | 2,000 | 726 · 484ms | 415 · 237ms | 2,770 · 1,250ms | 10,406 · 2,720ms |
|
|
60
|
+
* | 4,000 | 726 · 420ms | 415 · 211ms | 3,020 · 648ms | 20,806 · 3,105ms |
|
|
61
|
+
* | 8,000 | — | — | — | 41,606 · 7,597ms |
|
|
62
|
+
*
|
|
63
|
+
* Three of the four hold their DOM flat as rows grow, and for structural
|
|
64
|
+
* reasons that will not change: the gantt VIRTUALISES its task list and
|
|
65
|
+
* timeline window, the calendar month grid draws at most four events per day
|
|
66
|
+
* cell, and the map auto-clusters above 100 markers. Their cost in rows is the
|
|
67
|
+
* O(n) transform, not the DOM.
|
|
68
|
+
*
|
|
69
|
+
* `ObjectTree` is the outlier and therefore the constraint: it flattens every
|
|
70
|
+
* expanded node into the document, measured at a strictly linear **5.2 DOM
|
|
71
|
+
* elements per record**, with no virtualisation anywhere on that path.
|
|
72
|
+
*
|
|
73
|
+
* The budget applied to that: keep the WORST of the four inside ~10,000 DOM
|
|
74
|
+
* elements — an order of magnitude above Lighthouse's ~1,400-element
|
|
75
|
+
* "excessive DOM size" warning, and the last point at which the tree's mount
|
|
76
|
+
* stays under ~3s in an environment that does no layout and no paint at all.
|
|
77
|
+
* 2,000 rows is where that lands, measured rather than interpolated: 10,406
|
|
78
|
+
* elements. It is also ~10x the real application result set this card was
|
|
79
|
+
* filed from (186 rows of `duly_task`), so a genuine working view is nowhere
|
|
80
|
+
* near it, which is the property that keeps the note meaningful when it does
|
|
81
|
+
* appear.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ These are SHARED-BOX jsdom seconds, not browser wall clock, and jsdom
|
|
84
|
+
* does no layout or paint. The DOM-element counts are the environment-
|
|
85
|
+
* independent half and the reason the ratio between the four views is the
|
|
86
|
+
* load-bearing part of the reading, not the milliseconds. This table is a
|
|
87
|
+
* reading taken once, when objectui#7210 landed; nothing re-derives it.
|
|
88
|
+
*/
|
|
89
|
+
export declare const NON_GRID_ROW_CEILING = 2000;
|
|
90
|
+
/**
|
|
91
|
+
* The query keys a non-grid view sends to honour {@link NON_GRID_ROW_CEILING}:
|
|
92
|
+
* `$top` is the ceiling plus ONE probe row.
|
|
93
|
+
*
|
|
94
|
+
* This is the ONE place the `+ 1` is written (objectui#7508, ruling A′). It
|
|
95
|
+
* used to be a published constant beside the ceiling, which left the
|
|
96
|
+
* mechanism split in two — `$top` at each call site, the slice in
|
|
97
|
+
* {@link applyNonGridRowCeiling} — and gave every caller a number to get
|
|
98
|
+
* wrong. A caller now spreads this into its `find()` query and never spells
|
|
99
|
+
* the probe row itself.
|
|
100
|
+
*
|
|
101
|
+
* The `+ 1` is what makes truncation DETECTABLE, and a site that lost it would
|
|
102
|
+
* look correct and report nothing: with exactly `$top: NON_GRID_ROW_CEILING`,
|
|
103
|
+
* a result set of exactly 2,000 and one of 200,000 both come back as 2,000
|
|
104
|
+
* rows, and the only thing separating them is a `total` the adapter is not
|
|
105
|
+
* obliged to send (`QueryResult.total` is optional, and an adapter answering
|
|
106
|
+
* with a bare array carries none at all). One extra row makes the distinction
|
|
107
|
+
* a fact about the rows in hand.
|
|
108
|
+
*
|
|
109
|
+
* A fresh object per call, so a caller that mutates the query it built cannot
|
|
110
|
+
* change what the next caller sends.
|
|
111
|
+
*/
|
|
112
|
+
export declare function nonGridRowCeilingQuery(): {
|
|
113
|
+
$top: number;
|
|
114
|
+
};
|
|
115
|
+
/** What {@link applyNonGridRowCeiling} tells a caller about its result set. */
|
|
116
|
+
export interface NonGridCeilingResult<T = any> {
|
|
117
|
+
/** The rows to draw — never more than {@link NON_GRID_ROW_CEILING}. */
|
|
118
|
+
rows: T[];
|
|
119
|
+
/**
|
|
120
|
+
* The size of the whole filtered result set when the adapter reported one
|
|
121
|
+
* (`QueryResult.total`), otherwise `undefined`. `undefined` is NOT "not
|
|
122
|
+
* truncated" — see {@link NonGridCeilingResult.truncated}, which is decided
|
|
123
|
+
* by the rows in hand and never by this.
|
|
124
|
+
*/
|
|
125
|
+
total?: number;
|
|
126
|
+
/** `true` when the source had more rows than the ceiling allows drawn. */
|
|
127
|
+
truncated: boolean;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Cap a non-grid view's result set at {@link NON_GRID_ROW_CEILING} and report
|
|
131
|
+
* whether it had to.
|
|
132
|
+
*
|
|
133
|
+
* Truncation is decided by the PROBE ROW (`rows.length > NON_GRID_ROW_CEILING`
|
|
134
|
+
* against a query built by {@link nonGridRowCeilingQuery}), never by comparing
|
|
135
|
+
* against `total`: `total` is optional on `QueryResult` and absent entirely
|
|
136
|
+
* from a bare-array response, so a `total`-based test would silently stop
|
|
137
|
+
* reporting on exactly the adapters least likely to be paging correctly.
|
|
138
|
+
*
|
|
139
|
+
* The rows are read by `extractRecords`, so this sink accepts exactly the
|
|
140
|
+
* envelopes that helper does (objectui#6839) — pinned in
|
|
141
|
+
* `non-grid-row-ceiling.contractEnvelope-6839.test.ts`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function applyNonGridRowCeiling<T = any>(result: unknown): NonGridCeilingResult<T>;
|
|
@@ -0,0 +1,141 @@
|
|
|
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 { extractRecords } from './extract-records.js';
|
|
9
|
+
/**
|
|
10
|
+
* The platform's hard row ceiling for a NON-GRID visualisation — gantt,
|
|
11
|
+
* calendar, map and tree (objectui#7210, maintainer ruling a′, 2026-09-02).
|
|
12
|
+
*
|
|
13
|
+
* ## Why this lives in `@object-ui/core` (objectui#7508, ruling A′)
|
|
14
|
+
*
|
|
15
|
+
* The four views live in four packages, so whatever they share is public by
|
|
16
|
+
* construction: every package here has a single entry and no subpath export.
|
|
17
|
+
* The non-React half of the mechanism — this constant, the query it sends and
|
|
18
|
+
* {@link applyNonGridRowCeiling} — wraps this package's own
|
|
19
|
+
* `extractRecords`, and every one of the four already imports core helpers,
|
|
20
|
+
* so this is its home. `@object-ui/react` re-exports the three names it
|
|
21
|
+
* carried before, and keeps only the React half (`NonGridRowCeilingNote`).
|
|
22
|
+
*
|
|
23
|
+
* ## What was ruled, and what was rejected
|
|
24
|
+
*
|
|
25
|
+
* A non-grid visualisation may fetch the whole FILTERED result set, because a
|
|
26
|
+
* truthful range or layout needs all of it: a gantt cannot compute
|
|
27
|
+
* `min(start) → max(end)` from one page, and a map cannot fit a camera to
|
|
28
|
+
* markers it never received. What it may not do is fetch it UNBOUNDED — at
|
|
29
|
+
* 100k scheduled rows that is the whole table into the browser, with no knob
|
|
30
|
+
* reachable from view metadata, since these four requests never carried a
|
|
31
|
+
* `$top` for `pagination.pageSize` to set.
|
|
32
|
+
*
|
|
33
|
+
* So the fetch carries a ceiling, and the ceiling is a NAMED CONSTANT IN THE
|
|
34
|
+
* RENDERER — not an authorable view key. Three alternatives were considered
|
|
35
|
+
* and are not what landed:
|
|
36
|
+
*
|
|
37
|
+
* - a documentation note only — still the whole table into the browser;
|
|
38
|
+
* - truncate at `pagination.pageSize` — SILENT truncation, and it caps a
|
|
39
|
+
* complete schedule at one page;
|
|
40
|
+
* - an authorable `maxRows` key — a new permanent key every author must set.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ The direction the ruling names as dangerous is SILENT truncation: a
|
|
43
|
+
* cut-off schedule still looks like a schedule, a cut-off map still looks like
|
|
44
|
+
* a map. Nothing on screen distinguishes "these are all the records" from
|
|
45
|
+
* "these are the first 2,000 of 40,000" unless the view says so. That is why
|
|
46
|
+
* `NonGridRowCeilingNote` (`@object-ui/react`) is not decoration — crossing
|
|
47
|
+
* this ceiling quietly is the defect, and the note is the fix.
|
|
48
|
+
*
|
|
49
|
+
* ## Why 2,000, measured
|
|
50
|
+
*
|
|
51
|
+
* The ruling sets ONE constant across the four views, chosen after measuring
|
|
52
|
+
* them, so the binding view decides it. Measured in this repo's jsdom lane
|
|
53
|
+
* (`@testing-library/react`, real child views, inline `value` provider, mount
|
|
54
|
+
* to settled paint), DOM elements materialised and mount duration:
|
|
55
|
+
*
|
|
56
|
+
* | rows | gantt | calendar | map | tree |
|
|
57
|
+
* |------:|-----------:|----------:|---------------:|----------------:|
|
|
58
|
+
* | 250 | 726 · 314ms | 415 · 231ms | 478 · 152ms | 1,306 · 326ms |
|
|
59
|
+
* | 1,000 | 726 · 226ms | 415 · 235ms | 1,512 · 299ms | 5,206 · 1,025ms |
|
|
60
|
+
* | 2,000 | 726 · 484ms | 415 · 237ms | 2,770 · 1,250ms | 10,406 · 2,720ms |
|
|
61
|
+
* | 4,000 | 726 · 420ms | 415 · 211ms | 3,020 · 648ms | 20,806 · 3,105ms |
|
|
62
|
+
* | 8,000 | — | — | — | 41,606 · 7,597ms |
|
|
63
|
+
*
|
|
64
|
+
* Three of the four hold their DOM flat as rows grow, and for structural
|
|
65
|
+
* reasons that will not change: the gantt VIRTUALISES its task list and
|
|
66
|
+
* timeline window, the calendar month grid draws at most four events per day
|
|
67
|
+
* cell, and the map auto-clusters above 100 markers. Their cost in rows is the
|
|
68
|
+
* O(n) transform, not the DOM.
|
|
69
|
+
*
|
|
70
|
+
* `ObjectTree` is the outlier and therefore the constraint: it flattens every
|
|
71
|
+
* expanded node into the document, measured at a strictly linear **5.2 DOM
|
|
72
|
+
* elements per record**, with no virtualisation anywhere on that path.
|
|
73
|
+
*
|
|
74
|
+
* The budget applied to that: keep the WORST of the four inside ~10,000 DOM
|
|
75
|
+
* elements — an order of magnitude above Lighthouse's ~1,400-element
|
|
76
|
+
* "excessive DOM size" warning, and the last point at which the tree's mount
|
|
77
|
+
* stays under ~3s in an environment that does no layout and no paint at all.
|
|
78
|
+
* 2,000 rows is where that lands, measured rather than interpolated: 10,406
|
|
79
|
+
* elements. It is also ~10x the real application result set this card was
|
|
80
|
+
* filed from (186 rows of `duly_task`), so a genuine working view is nowhere
|
|
81
|
+
* near it, which is the property that keeps the note meaningful when it does
|
|
82
|
+
* appear.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ These are SHARED-BOX jsdom seconds, not browser wall clock, and jsdom
|
|
85
|
+
* does no layout or paint. The DOM-element counts are the environment-
|
|
86
|
+
* independent half and the reason the ratio between the four views is the
|
|
87
|
+
* load-bearing part of the reading, not the milliseconds. This table is a
|
|
88
|
+
* reading taken once, when objectui#7210 landed; nothing re-derives it.
|
|
89
|
+
*/
|
|
90
|
+
export const NON_GRID_ROW_CEILING = 2000;
|
|
91
|
+
/**
|
|
92
|
+
* The query keys a non-grid view sends to honour {@link NON_GRID_ROW_CEILING}:
|
|
93
|
+
* `$top` is the ceiling plus ONE probe row.
|
|
94
|
+
*
|
|
95
|
+
* This is the ONE place the `+ 1` is written (objectui#7508, ruling A′). It
|
|
96
|
+
* used to be a published constant beside the ceiling, which left the
|
|
97
|
+
* mechanism split in two — `$top` at each call site, the slice in
|
|
98
|
+
* {@link applyNonGridRowCeiling} — and gave every caller a number to get
|
|
99
|
+
* wrong. A caller now spreads this into its `find()` query and never spells
|
|
100
|
+
* the probe row itself.
|
|
101
|
+
*
|
|
102
|
+
* The `+ 1` is what makes truncation DETECTABLE, and a site that lost it would
|
|
103
|
+
* look correct and report nothing: with exactly `$top: NON_GRID_ROW_CEILING`,
|
|
104
|
+
* a result set of exactly 2,000 and one of 200,000 both come back as 2,000
|
|
105
|
+
* rows, and the only thing separating them is a `total` the adapter is not
|
|
106
|
+
* obliged to send (`QueryResult.total` is optional, and an adapter answering
|
|
107
|
+
* with a bare array carries none at all). One extra row makes the distinction
|
|
108
|
+
* a fact about the rows in hand.
|
|
109
|
+
*
|
|
110
|
+
* A fresh object per call, so a caller that mutates the query it built cannot
|
|
111
|
+
* change what the next caller sends.
|
|
112
|
+
*/
|
|
113
|
+
export function nonGridRowCeilingQuery() {
|
|
114
|
+
return { $top: NON_GRID_ROW_CEILING + 1 };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Cap a non-grid view's result set at {@link NON_GRID_ROW_CEILING} and report
|
|
118
|
+
* whether it had to.
|
|
119
|
+
*
|
|
120
|
+
* Truncation is decided by the PROBE ROW (`rows.length > NON_GRID_ROW_CEILING`
|
|
121
|
+
* against a query built by {@link nonGridRowCeilingQuery}), never by comparing
|
|
122
|
+
* against `total`: `total` is optional on `QueryResult` and absent entirely
|
|
123
|
+
* from a bare-array response, so a `total`-based test would silently stop
|
|
124
|
+
* reporting on exactly the adapters least likely to be paging correctly.
|
|
125
|
+
*
|
|
126
|
+
* The rows are read by `extractRecords`, so this sink accepts exactly the
|
|
127
|
+
* envelopes that helper does (objectui#6839) — pinned in
|
|
128
|
+
* `non-grid-row-ceiling.contractEnvelope-6839.test.ts`.
|
|
129
|
+
*/
|
|
130
|
+
export function applyNonGridRowCeiling(result) {
|
|
131
|
+
const all = extractRecords(result);
|
|
132
|
+
const rawTotal = result && typeof result === 'object' && typeof result.total === 'number'
|
|
133
|
+
? result.total
|
|
134
|
+
: undefined;
|
|
135
|
+
const truncated = all.length > NON_GRID_ROW_CEILING;
|
|
136
|
+
return {
|
|
137
|
+
rows: truncated ? all.slice(0, NON_GRID_ROW_CEILING) : all,
|
|
138
|
+
total: rawTotal,
|
|
139
|
+
truncated,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
+
import type { ViewType } from '@object-ui/types';
|
|
8
9
|
import type { RowHeight } from '@objectstack/spec/ui';
|
|
9
10
|
/** ListView's toolbar density vocabulary — three steps, not the spec's five. */
|
|
10
11
|
export type DensityMode = 'compact' | 'comfortable' | 'spacious';
|
|
@@ -62,6 +63,101 @@ export declare const ROW_HEIGHT_TO_DENSITY_MODE: Record<RowHeight, DensityMode>;
|
|
|
62
63
|
* something typed `DensityMode`.
|
|
63
64
|
*/
|
|
64
65
|
export declare function rowHeightToDensityMode(rowHeight: unknown): DensityMode | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* A visualization `ListView` actually draws — one member per `case` in its
|
|
68
|
+
* `viewComponentSchema` switch (`packages/plugin-list/src/ListView.tsx`).
|
|
69
|
+
*
|
|
70
|
+
* DERIVED from {@link ViewType} (`ca3942729`), which is itself derived from
|
|
71
|
+
* `@objectstack/spec/ui` `ListView['type']`. Spelled as an `Exclude` of the
|
|
72
|
+
* members this renderer does not draw rather than as the spec's own
|
|
73
|
+
* `VisualizationType`, deliberately: `Exclude` leaves the SPEC's list as the
|
|
74
|
+
* thing being subtracted from, so ANY member the spec adds — visualization or
|
|
75
|
+
* not — lands here as a required key and fails the build, which is what the
|
|
76
|
+
* promise below actually says. Keying on `VisualizationType` instead would let
|
|
77
|
+
* a future non-visualization list type (another `page`) pass this site in
|
|
78
|
+
* silence, which is the exact failure being fixed.
|
|
79
|
+
*
|
|
80
|
+
* ⭐ What it subtracts is {@link UndrawableViewKind} — the keys of
|
|
81
|
+
* {@link UNDRAWABLE_VIEW_KINDS} the current `ViewType` still carries — and
|
|
82
|
+
* never a literal list of kinds (objectui#9880). A literal list is a second
|
|
83
|
+
* hand-written copy of the vocabulary, the failure class `ca3942729` removed
|
|
84
|
+
* from `ViewType` itself, and it rots in the direction the spec actually moved:
|
|
85
|
+
* `Exclude<ViewType, 'list' | 'detail' | 'page'>` kept subtracting `'page'`
|
|
86
|
+
* after objectstack RETIRED it, where `Exclude` of a non-member is a silent
|
|
87
|
+
* no-op no compiler reads. The sibling record's excess `page:` key is what went
|
|
88
|
+
* red; this line stayed green while saying something that had stopped being
|
|
89
|
+
* true.
|
|
90
|
+
*
|
|
91
|
+
* It happens to equal `@objectstack/spec/ui`'s `VisualizationType` today. That
|
|
92
|
+
* is a fact about the resolved spec, not the definition.
|
|
93
|
+
*/
|
|
94
|
+
export type ListViewVisualization = Exclude<ViewType, UndrawableViewKind>;
|
|
95
|
+
/**
|
|
96
|
+
* Every member of {@link ViewType} that {@link LIST_VIEW_KINDS} does NOT carry,
|
|
97
|
+
* with the reason — and, where the answer is "the author authored something
|
|
98
|
+
* valid and will get a grid", the sentence to say out loud.
|
|
99
|
+
*
|
|
100
|
+
* The two tables together still partition the derived union — a member the spec
|
|
101
|
+
* ADDS must be classed as drawable or explained HERE, and until it is the build
|
|
102
|
+
* is red — but the red lands on {@link LIST_VIEW_KINDS} alone, because a kind
|
|
103
|
+
* neither table classes is not extracted into {@link UndrawableViewKind} and so
|
|
104
|
+
* stays inside {@link ListViewVisualization}, whose record is total. A key
|
|
105
|
+
* MISSPELLED here fails the same way, for the same reason: the kind it was
|
|
106
|
+
* meant to class is not extracted either. Both directions are pinned in
|
|
107
|
+
* `__tests__/normalize-list-view.undrawableBothLegs-9880.test.ts` rather than
|
|
108
|
+
* asserted in this sentence.
|
|
109
|
+
*
|
|
110
|
+
* ⛔ NOT annotated `Record<Exclude<ViewType, ListViewVisualization>, …>`, and
|
|
111
|
+
* the `satisfies` is not a style choice (objectui#9880). An annotation makes
|
|
112
|
+
* the literal EXACT in BOTH directions: a kind the spec adds is a missing key
|
|
113
|
+
* (wanted) and a kind the spec RETIRES is an EXCESS PROPERTY — a TS2353 that
|
|
114
|
+
* demands a deletion this repository cannot make while it resolves a published
|
|
115
|
+
* spec which still carries the kind (as it did with `page` through 17.4.0).
|
|
116
|
+
* `satisfies` keeps the value constraint
|
|
117
|
+
* (every reason is a `string` or `null`) and drops the exactness, which is what
|
|
118
|
+
* lets one spelling compile against both the pinned published spec and one
|
|
119
|
+
* built from objectstack `main`.
|
|
120
|
+
*
|
|
121
|
+
* `null` means silence is correct — these are objectui view CATEGORIES, never
|
|
122
|
+
* authored as a list-view kind:
|
|
123
|
+
* - `list` is the view CATEGORY, not a kind — it already folds to `grid`.
|
|
124
|
+
* - `detail` is a different renderer (`plugin-detail`), never a ListView case.
|
|
125
|
+
*
|
|
126
|
+
* `page` had a row here until objectui#11073. objectstack#17063 removed
|
|
127
|
+
* `type: 'page'` from the list-view enum, and the row answered for it while this
|
|
128
|
+
* repository still resolved a spec that published the kind. `@objectstack/spec`
|
|
129
|
+
* 17.5.0 carries the removal: both published `@object-ui/types` faces refuse
|
|
130
|
+
* `page`, `Extract` no longer extracted the row, and the residual pins that held
|
|
131
|
+
* it (`normalize-list-view.pageResidual-8429.test.ts`) were converted, so it was
|
|
132
|
+
* deleted as it said it would be. A stored `page` view now degrades like any
|
|
133
|
+
* kind outside the vocabulary.
|
|
134
|
+
*/
|
|
135
|
+
declare const UNDRAWABLE_VIEW_KINDS: {
|
|
136
|
+
list: null;
|
|
137
|
+
detail: null;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* The members of the current {@link ViewType} that {@link UNDRAWABLE_VIEW_KINDS}
|
|
141
|
+
* classes — the other half of the partition, derived rather than written out.
|
|
142
|
+
*
|
|
143
|
+
* `Extract` over the table's keys, not the table's raw `keyof`, is the load-
|
|
144
|
+
* bearing operator (objectui#9880): the table may carry a row for a kind the
|
|
145
|
+
* spec has since RETIRED, and `Extract` drops that row from the TYPE without
|
|
146
|
+
* the table having to lose it at runtime. That is what made one spelling
|
|
147
|
+
* correct against both the published spec (which published `page` through
|
|
148
|
+
* 17.4.0) and a spec built from objectstack `main` (which retired it), and what
|
|
149
|
+
* made the row's deletion at 17.5.0 a plain deletion (objectui#11073).
|
|
150
|
+
*/
|
|
151
|
+
type UndrawableViewKind = Extract<ViewType, keyof typeof UNDRAWABLE_VIEW_KINDS>;
|
|
152
|
+
/**
|
|
153
|
+
* Whether `value` names a visualization `ListView` draws.
|
|
154
|
+
*
|
|
155
|
+
* Exported so a read site cannot restate the membership question as its own
|
|
156
|
+
* literal array: the gate and the seam must answer one question, which is the
|
|
157
|
+
* rule this file's own `availableViews` notes already argue for (objectui#5042,
|
|
158
|
+
* objectui#7544, and now `ca3942729`).
|
|
159
|
+
*/
|
|
160
|
+
export declare function isListViewVisualization(value: unknown): value is ListViewVisualization;
|
|
65
161
|
/**
|
|
66
162
|
* ObjectUI's `list-view` node historically used a different vocabulary from
|
|
67
163
|
* `@objectstack/spec` for the same concepts (`fields` where the spec says
|
|
@@ -114,10 +210,34 @@ export declare function rowHeightToDensityMode(rowHeight: unknown): DensityMode
|
|
|
114
210
|
* so objectui's field is typed from the spec but used as something else.
|
|
115
211
|
* That mismatch is real and out of scope here; converting formats inside a
|
|
116
212
|
* vocabulary fold would change what reaches the data source.
|
|
213
|
+
* - `data: { provider: 'object', object }` → `objectName` (#7477, step 6 of
|
|
214
|
+
* #2890). This is the spelling the published `react-blocks` contract
|
|
215
|
+
* recommends and the one `@objectstack/spec`'s `ViewDataSchema` declares;
|
|
216
|
+
* ListView read it at ZERO sites, so a page bound that way validated green
|
|
217
|
+
* upstream and rendered an empty list here with no diagnostic. The `object`
|
|
218
|
+
* provider is a `strictObject` carrying exactly `{ provider, object }`, so
|
|
219
|
+
* `objectName` captures all of it. Two deliberate departures from the folds
|
|
220
|
+
* above, both narrowing:
|
|
221
|
+
* · an already-present `objectName` WINS — this fold only fills a gap, it
|
|
222
|
+
* never re-points a binding that already resolves;
|
|
223
|
+
* · `data` is NOT deleted. Every other fold deletes because the legacy key
|
|
224
|
+
* has one meaning and one home; `data` has four providers, `api`/`value`
|
|
225
|
+
* are read live in `ListView`, and the whole block is FORWARDED to child
|
|
226
|
+
* views (the gantt branch), whose own `getDataConfig` reads `data` before
|
|
227
|
+
* `objectName`. Deleting it for one provider would rewrite what a child
|
|
228
|
+
* resolves. Nothing in `ListView` reads `data.provider === 'object'`, so
|
|
229
|
+
* keeping it cannot create a second de-facto contract inside the renderer.
|
|
117
230
|
* - `viewType`: a missing kind, or the view CATEGORY `'list'` that AI-authored
|
|
118
231
|
* metadata stores and hosts forward verbatim, becomes the renderable `'grid'`
|
|
119
232
|
* — otherwise it reaches the renderer's typeless default branch and shows as
|
|
120
|
-
* a red "Unknown component type" box.
|
|
233
|
+
* a red "Unknown component type" box. Before that default applies, the
|
|
234
|
+
* AUTHOR's kind is read (#7477): `specType` — the slot
|
|
235
|
+
* `components/renderers/layout/react-page.tsx` parks a react-tier `type` in
|
|
236
|
+
* when the SDUI envelope claims the `type` key (ADR-0078) — and then a bare
|
|
237
|
+
* `type` when it names a kind ListView draws, which the component
|
|
238
|
+
* discriminator (`'list-view'`) never does. Same two legs, same order, as
|
|
239
|
+
* `normalizeChartSchema`'s chart-family read. An explicit `viewType` still
|
|
240
|
+
* wins: this leg only fills the gap that used to resolve to `'grid'`.
|
|
121
241
|
* - each `columns` entry's IDENTITY — `name` / `fieldName` → the spec's `field`
|
|
122
242
|
* (#3104). This one MIRRORS instead of deleting, for the reason given on
|
|
123
243
|
* {@link normalizeColumnIdentities}: `columns` entries cross the package
|