@object-ui/plugin-dashboard 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 +4519 -0
- package/README.md +424 -83
- package/dist/DashboardConfigPanel.d.ts +1 -1
- package/dist/DashboardFilterBar.d.ts.map +1 -1
- package/dist/DashboardGridLayout.d.ts +3 -2
- package/dist/DashboardGridLayout.d.ts.map +1 -1
- package/dist/DashboardRenderer.d.ts +32 -2
- package/dist/DashboardRenderer.d.ts.map +1 -1
- package/dist/DashboardWithConfig.d.ts +1 -1
- package/dist/DashboardWithConfig.d.ts.map +1 -1
- package/dist/DatasetWidget.d.ts +42 -14
- package/dist/DatasetWidget.d.ts.map +1 -1
- package/dist/DrillDownDrawer.d.ts +14 -5
- package/dist/DrillDownDrawer.d.ts.map +1 -1
- package/dist/MetricCard.d.ts +1 -1
- package/dist/MetricWidget.d.ts +27 -2
- package/dist/MetricWidget.d.ts.map +1 -1
- package/dist/ObjectDataTable.d.ts +407 -18
- package/dist/ObjectDataTable.d.ts.map +1 -1
- package/dist/ObjectMetricWidget.d.ts +35 -8
- package/dist/ObjectMetricWidget.d.ts.map +1 -1
- package/dist/ObjectPivotTable.d.ts +46 -7
- package/dist/ObjectPivotTable.d.ts.map +1 -1
- package/dist/PivotTable.d.ts +20 -3
- package/dist/PivotTable.d.ts.map +1 -1
- package/dist/RecordDetailDrawer.d.ts +8 -0
- package/dist/RecordDetailDrawer.d.ts.map +1 -1
- package/dist/WidgetConfigPanel.d.ts +2 -2
- package/dist/WidgetConfigPanel.d.ts.map +1 -1
- package/dist/WidgetEmptyState.d.ts +27 -0
- package/dist/WidgetEmptyState.d.ts.map +1 -0
- package/dist/domPassthroughPins.d.ts.map +1 -1
- package/dist/index.d.ts +22 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1733 -1570
- package/dist/index.umd.cjs +2 -2
- package/dist/legacyRetiredWidget.d.ts +5 -3
- package/dist/legacyRetiredWidget.d.ts.map +1 -1
- package/dist/measureRefusal.d.ts +50 -0
- package/dist/measureRefusal.d.ts.map +1 -0
- package/dist/recordFields.d.ts +119 -8
- package/dist/recordFields.d.ts.map +1 -1
- package/dist/retired-node-types.d.ts +29 -0
- package/dist/retired-node-types.d.ts.map +1 -0
- package/dist/useDashboardAutoRefresh.d.ts +47 -0
- package/dist/useDashboardAutoRefresh.d.ts.map +1 -0
- package/dist/utils.d.ts +36 -7
- package/dist/utils.d.ts.map +1 -1
- package/dist/widgetDispatch.d.ts +144 -18
- package/dist/widgetDispatch.d.ts.map +1 -1
- package/dist/widgetSubCaption.d.ts +40 -0
- package/dist/widgetSubCaption.d.ts.map +1 -0
- package/package.json +15 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,4524 @@
|
|
|
1
1
|
# @object-ui/plugin-dashboard
|
|
2
2
|
|
|
3
|
+
## 17.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0651e7a: The grid summary footer and the dashboard metric tile take a currency amount's
|
|
8
|
+
decimal places from the currency, never from `scale`, and the field designer no
|
|
9
|
+
longer offers `Scale` on a currency field (objectui#10221).
|
|
10
|
+
|
|
11
|
+
The currency arm of the grid column-summary footer (`useColumnSummary` in
|
|
12
|
+
`@object-ui/plugin-grid`) and the currency face of `ObjectMetricWidget` in
|
|
13
|
+
`@object-ui/plugin-dashboard` each took the number of decimal places from the
|
|
14
|
+
field's `scale`, with none when `scale` was absent. The currency list cell
|
|
15
|
+
never read `scale`. So a USD column declaring no `scale` totalled
|
|
16
|
+
`1234.5` as `$1,235` under cells reading `$1,234.50`, and a JPY field carrying
|
|
17
|
+
`scale: 4` showed `¥1,234.5000`.
|
|
18
|
+
|
|
19
|
+
Both faces now format an amount in a resolved currency with `formatCurrency`
|
|
20
|
+
from `@object-ui/fields`, the list cell's own formatter, so footer, tile and
|
|
21
|
+
cell agree. The width is the resolved currency's own ISO 4217 minor-unit count: two
|
|
22
|
+
decimals for USD or CNY, none for JPY, three for KWD. A whole amount shows no
|
|
23
|
+
fraction (`$1,234`), as the cell does. When no currency resolves, the amount is
|
|
24
|
+
a plain number with two decimals, or none when it is whole, as the cell renders
|
|
25
|
+
it. Neither `scale` nor
|
|
26
|
+
`precision` is read on a currency. This follows the maintainer ruling recorded
|
|
27
|
+
on objectstack-ai/objectstack#19910 that a currency's decimal places are the
|
|
28
|
+
currency's, not a setting, and the ruling on objectstack-ai/objectstack#19629
|
|
29
|
+
that takes `scale` off the `currency` type. The footer keeps its existing
|
|
30
|
+
tenant-locale fallback for a currency code `Intl` does not accept, and a metric
|
|
31
|
+
tile with an authored `format` pattern still renders that pattern.
|
|
32
|
+
|
|
33
|
+
The field designer's numeric section (`ObjectFieldInspector` in
|
|
34
|
+
`@object-ui/app-shell`) no longer offers a `Scale` control on a `currency`
|
|
35
|
+
field. `number` and `percent` fields keep it. The `Precision` control stays on
|
|
36
|
+
all three: it writes the field-level `precision`, the total digit count of the
|
|
37
|
+
stored decimal, not a number of decimal places.
|
|
38
|
+
|
|
39
|
+
**Behaviour change** for currency amounts on the footer and the tile, filed as
|
|
40
|
+
`minor` because this repo's fixed release group forbids `major`. Where the
|
|
41
|
+
field's `scale` differed from what the cell renders, the output moves:
|
|
42
|
+
|
|
43
|
+
- `scale: 2` on a JPY field used to show `¥1,234.50` and now shows `¥1,235`.
|
|
44
|
+
- A USD field with no `scale` used to show `$1,235` for `1234.5` and now shows
|
|
45
|
+
`$1,234.50`.
|
|
46
|
+
- A whole USD amount under `scale: 2` used to show `$1,234.00` and now shows
|
|
47
|
+
`$1,234`, like the cells.
|
|
48
|
+
|
|
49
|
+
Percent columns and fields still take their width from `scale`.
|
|
50
|
+
|
|
51
|
+
**Migration.** Nothing to restate: a currency amount's decimal places follow its
|
|
52
|
+
currency. Delete `scale` from currency fields. A dashboard that wants a fixed
|
|
53
|
+
pattern on a metric tile declares it with `format`.
|
|
54
|
+
|
|
55
|
+
⚠️ **Dated note, 2026-10-02 — the whole-amount sentences above were made false in this same release — objectui#11444.**
|
|
56
|
+
The sentences saying a whole amount shows no fraction (`$1,234`, "as the cell does"), that an amount with no currency resolved has no decimals when it is whole, and the bullet saying a whole USD amount under `scale: 2` now shows `$1,234`, no longer hold. Later in this release triage ruled that a currency's declared width, its ISO 4217 minor-unit count, is the protocol's convention, and `formatCurrency` stopped dropping the fraction of a whole amount (objectui#11444, comment 5946462862). The footer and the tile still take `formatCurrency` by reference, so they move with the cell: a whole USD amount reads `$1,234.00`, a whole amount with no currency resolved reads `1,234.00`, and a whole JPY amount still reads `¥1,234`. The rest of this entry is kept as the reading of this change.
|
|
57
|
+
- 6dc82a3: fix(plugin-dashboard): a metric tile counting rows over a currency field shows a plain number, not money
|
|
58
|
+
|
|
59
|
+
`ObjectMetricWidget` decided to dress its number as money from the aggregated
|
|
60
|
+
field's TYPE alone, so `aggregate: { field: 'amount', function: 'count' }` over a
|
|
61
|
+
`currency` field showed `$3` for three records — a number of rows read as an
|
|
62
|
+
amount. The grid footer already reads the same count on a currency column as a
|
|
63
|
+
plain `3`.
|
|
64
|
+
|
|
65
|
+
The field's unit now applies only to an aggregate that answers in it: `sum`,
|
|
66
|
+
`avg`, `min` and `max` keep the currency face exactly as before (the list cell's
|
|
67
|
+
own `formatCurrency`), while `count` and `count_distinct` render as plain,
|
|
68
|
+
locale-formatted numbers. The same rule withholds a `percent` field's pattern
|
|
69
|
+
from a count, so three rows no longer read `3%` either — the footer's rule for a
|
|
70
|
+
percent column.
|
|
71
|
+
|
|
72
|
+
Visible behaviour change for dashboards that count over a currency or percent
|
|
73
|
+
field: the tile loses its currency symbol or percent sign. An authored `format`
|
|
74
|
+
or `currency` on the tile is the author's declaration and is not second-guessed.
|
|
75
|
+
- 37140f4: refactor(plugin-dashboard)!: retire the `dashboard-grid` node type key, and register the `metric` / `metric-card` node keys without a bare fallback (objectui#10859, batch 8 phase 2b)
|
|
76
|
+
|
|
77
|
+
**BREAKING (authoring):**
|
|
78
|
+
|
|
79
|
+
- **`dashboard-grid` is unregistered** (with `plugin-dashboard:dashboard-grid`). It was builder chrome published as a node key: `objectui validate` refused it at `type`, and nothing authored or emitted it. `DashboardGridLayout` stays a named export; mount it directly.
|
|
80
|
+
- **`metric` and `metric-card` register with `skipFallback: true`.** Both dashboard surfaces (`DashboardRenderer`, `DashboardGridLayout`) now hand `SchemaRenderer` `plugin-dashboard:metric` / `plugin-dashboard:metric-card` through one table, `DASHBOARD_NODE_TYPES` in `widgetDispatch.ts`. That covers the static metric tile, the `metric-card` widget-slot entry and a `metric` / `metric-card` node in a widget's legacy `component` envelope. The dashboard WIDGET vocabulary is unchanged: `widgets[].type: 'metric'` and the 2026-08-14 `metric-card` slot entry author exactly as before.
|
|
81
|
+
- **`dashboardComponents`** drops `dashboard-grid`, and its `metric` / `metric-card` entries are keyed `plugin-dashboard:metric` / `plugin-dashboard:metric-card`, the type each now serves, so iterating the map cannot re-create the retired bare keys.
|
|
82
|
+
- A rendered KPI tile's `data-obj-type` reads `plugin-dashboard:metric` (or `plugin-dashboard:metric-card`) instead of the bare spelling.
|
|
83
|
+
|
|
84
|
+
Migration:
|
|
85
|
+
|
|
86
|
+
- `{ "type": "dashboard-grid", "widgets": [ … ] }` → `{ "type": "dashboard", "widgets": [ … ] }`, or mount `DashboardGridLayout` directly for the drag/resize editor.
|
|
87
|
+
- a standalone `{ "type": "metric", … }` node → `{ "type": "plugin-dashboard:metric", … }`; inside a dashboard, keep authoring the `metric` widget.
|
|
88
|
+
- a standalone `{ "type": "metric-card", … }` node → put it in a dashboard's `widgets[]` (the slot entry), or write `plugin-dashboard:metric-card`.
|
|
89
|
+
- `dashboardComponents['metric']` / `dashboardComponents['metric-card']` → `dashboardComponents['plugin-dashboard:metric']` / `dashboardComponents['plugin-dashboard:metric-card']`; `dashboardComponents['dashboard-grid']` → import `DashboardGridLayout`.
|
|
90
|
+
- a selector `[data-obj-type="metric"]` / `[data-obj-type="metric-card"]` → `[data-obj-type="plugin-dashboard:metric"]` / `[data-obj-type="plugin-dashboard:metric-card"]`.
|
|
91
|
+
|
|
92
|
+
**Clause-②: yes** — registrations leave the runtime (narrowing), released as `minor` with this banner.
|
|
93
|
+
|
|
94
|
+
⚠️ **Dated note, 2026-10-02 — `metric-card` leaves the widget vocabulary — objectui#11483.** At this change, "The dashboard WIDGET vocabulary is unchanged" held, and that vocabulary named `metric-card`. Now the widget `type` vocabulary (`DashboardWidgetTypeSchema` / `DashboardWidgetTypeName`) names no component type. The `metric-card` slot entry is still authored as before when it carries its `value`; without one it is refused. `.changeset/11483-metric-card-needs-value.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
95
|
+
- cc4e476: `drillDown` is retired on the bare `pivot` node: author `object-pivot` to drill (objectui#10932)
|
|
96
|
+
|
|
97
|
+
**Breaking for authored metadata, graded `minor` by this repo's convention (AGENTS.md: a
|
|
98
|
+
breaking change here is `minor`, never `major`):** a `pivot` node no longer accepts
|
|
99
|
+
`drillDown` on either published face. The TypeScript `PivotTableSchema.drillDown` is a
|
|
100
|
+
`?: never` tombstone, and the `pivot` arm of the zod mirror, the door `objectui validate` /
|
|
101
|
+
`safeValidateSchema` run, refuses the key by name with `invalid_type` at `drillDown`. The
|
|
102
|
+
refusal names the remedy. Migration: delete the key, or, for a pivot whose cells open the
|
|
103
|
+
records behind a value, author an `object-pivot` (`objectName` plus the same `rowField` /
|
|
104
|
+
`columnField` / `valueField`) and put the `drillDown` there. Every other `pivot` member is
|
|
105
|
+
accepted exactly as before.
|
|
106
|
+
|
|
107
|
+
Nothing ever honoured the key on this node. `PivotTable` fired a drill only when its host
|
|
108
|
+
passed an `onDrillDown` handler. The one host that does is `ObjectPivotTable`, which renders
|
|
109
|
+
`object-pivot`, and the `pivot` registration passes none. So an authored `drillDown`
|
|
110
|
+
validated and clicking a cell did nothing, with no signal. A pivot over inline `data` also
|
|
111
|
+
names no object to list the drilled records from. No shipped or example document authors
|
|
112
|
+
the key on a `pivot` node.
|
|
113
|
+
|
|
114
|
+
This supersedes what three pending entries say about the `pivot` arm's `drillDown`: the
|
|
115
|
+
objectui#10859 (batch 2) entry ("`drillDown` is the shared `DrillDownConfigSchema`"), and the
|
|
116
|
+
dated notes on the objectui#7352 and objectui#10685 entries, which read that key as the shared
|
|
117
|
+
mirror (a third referencing declaration, and `mode` accepted there). As of this change the
|
|
118
|
+
arm refuses the key.
|
|
119
|
+
|
|
120
|
+
- `@object-ui/types`: `PivotTableSchema.drillDown` is `?: never`, and the zod `pivot` arm's
|
|
121
|
+
`drillDown` is a `retirementTombstone()`. The content-channel refusal on `body` / `children`
|
|
122
|
+
no longer lists `drillDown` among what a `pivot` renders.
|
|
123
|
+
- `@object-ui/plugin-dashboard`: `PivotTable` reads nothing off `schema` for its drill.
|
|
124
|
+
**Behaviour change for React callers:** its `onDrillDown` prop is now the only switch. A
|
|
125
|
+
caller that passes a handler gets interactive cells, headers and totals, and a
|
|
126
|
+
`schema.drillDown` no longer gates them (the type refuses that key anyway). A caller that
|
|
127
|
+
wants no drill passes no handler. `ObjectPivotTable` already passed its handler only when
|
|
128
|
+
its own `drillDown` was enabled, so `object-pivot` behaves exactly as before, and its
|
|
129
|
+
`schema.drillDown` keeps the `ObjectPivotDrillDownConfig` type.
|
|
130
|
+
- 27ae632: A `record:path` whose stage labels are per-locale maps now shows the viewer's language instead of failing to render, and five label inputs on `object-metric`, `object-grid` and `record:related_list` now accept the per-locale map their renderers already resolve (objectui#10993, batch 2).
|
|
131
|
+
|
|
132
|
+
**`record:path`, `stages[].label`.** `@objectstack/spec` types each stage's `label` as `I18nLabel`: a plain string or an inline per-locale map such as `{ en: 'Draft', 'zh-CN': '草稿' }`. `RecordPathRenderer` read it raw, as the stage's visible text and inside the stage's accessible name, so a map threw "Objects are not valid as a React child" and the node rendered `Component "record:path" failed to render` instead of the path. It now resolves each map with `pickLocalized` against the active UI language (`useObjectTranslation().language`) before the stages are translated, classified or drawn:
|
|
133
|
+
|
|
134
|
+
- Both rows (desktop and mobile) paint the entry for the viewer's language, and each stage's accessible name carries the same text beside its state words, in one language.
|
|
135
|
+
- A stage with no `terminal` is still classified from its label: a lost stage whose label is a map (for example `{ en: 'Closed Lost', 'zh-CN': '丢单' }`) still reads as lost.
|
|
136
|
+
- A plain string renders exactly as authored. A picklist translation of the status field still outranks the authored label, as it did for a string.
|
|
137
|
+
|
|
138
|
+
**The registry inputs.** These inputs declared `type: 'string'` only, while their renderers already resolve a per-locale map, so the manifest built from `ComponentRegistry.getPublicConfigs()` made `validateTree` report `type-mismatch` on a map the spec accepts. Each now declares both arms, `type: ['string', 'object']`, with a description that teaches the map:
|
|
139
|
+
|
|
140
|
+
- `@object-ui/plugin-dashboard`: `object-metric`'s `label` (the tile heading), `description` (the sub-caption under the value) and `title` (the drill-down panel heading), resolved against the active UI language.
|
|
141
|
+
- `@object-ui/plugin-grid`: `object-grid`'s `label` (the table caption, the export title and the record-detail overlay heading), resolved against the display locale: the workspace's regional default when one is configured, otherwise the active UI language. `view:grid` registers the same inputs, so its declared `label` carries the same two arms.
|
|
142
|
+
- `@object-ui/plugin-detail`: `record:related_list`'s `title` (the list heading), resolved against the active UI language.
|
|
143
|
+
|
|
144
|
+
A value that matches neither arm, such as a number, is still reported. No renderer changes for these five: each already resolved the map.
|
|
145
|
+
|
|
146
|
+
**Clause-②: yes** — five published registry inputs widen from `'string'` to `['string', 'object']`, so `validateTree` accepts a locale map on them (and `view:grid`'s `label`, which shares `object-grid`'s inputs, declares the same arms). Nothing that was accepted before is refused now.
|
|
147
|
+
- b28bde8: fix(plugin-dashboard): a served dashboard draws its own title, description and sub-caption, not the packaged catalog's (objectui#11295)
|
|
148
|
+
|
|
149
|
+
A dashboard read from the server's `/meta` route is already translated for the
|
|
150
|
+
request's locale. Since objectstack#20680 the server also keeps a published edit
|
|
151
|
+
over the packaged catalog: an explicit override beats the packaged default.
|
|
152
|
+
`DashboardRenderer` still ran the client bundle over the served strings. It passed
|
|
153
|
+
each served widget title, description and sub-caption to the bundle as the
|
|
154
|
+
fallback, and a bundle entry beats its fallback. So the packaged catalog won again
|
|
155
|
+
on the client, and a published edit drew as the shipped string, in `en` as well as
|
|
156
|
+
`zh-CN`.
|
|
157
|
+
|
|
158
|
+
`DashboardRenderer` gains a `localized` prop. A host that read its document from
|
|
159
|
+
`/meta` sets it, and the console's dashboard page does. With it, these texts are
|
|
160
|
+
drawn as given: the widget `title`, `description` and sub-caption
|
|
161
|
+
(`options.description`), and the renderer's own header `label` and `description`.
|
|
162
|
+
An inline per-locale map is still collapsed to the active language.
|
|
163
|
+
`useWidgetSubCaption` takes the same flag as an optional second argument.
|
|
164
|
+
|
|
165
|
+
What does not change:
|
|
166
|
+
|
|
167
|
+
- Without the prop, the bundle composition is unchanged. That covers a page's
|
|
168
|
+
inline `dashboard` block, a preview and a design surface, none of which the
|
|
169
|
+
server translated.
|
|
170
|
+
- Header-action labels keep their bundle lookup either way, because the server
|
|
171
|
+
does not translate them.
|
|
172
|
+
- 1a88ce2: **BREAKING (rendering):** a dataset-bound dashboard widget no longer reads `chartConfig.series`, `chartConfig.xAxis` or `chartConfig.yAxis` (objectui#11315).
|
|
173
|
+
|
|
174
|
+
`@objectstack/spec` 17.5.0 refuses these keys on a dashboard widget's chart config
|
|
175
|
+
(`DashboardWidgetChartConfigSchema`, ADR-0021 · ADR-0049 D2), together with `chartConfig.type`, which
|
|
176
|
+
the widget never read. Until now the dataset widget still merged their presentation half onto the
|
|
177
|
+
dataset-derived chart: a per-series mark and axis side from `series[]`, and axis titles, number
|
|
178
|
+
formats, min/max and a second value axis from `xAxis` / `yAxis`.
|
|
179
|
+
|
|
180
|
+
- A widget that still carries the keys (stored metadata that was never re-validated) now renders
|
|
181
|
+
the dataset's own derivation: one series per measure in `values`, the category axis from
|
|
182
|
+
`dimensions`, and no authored axis.
|
|
183
|
+
- A `combo` widget still draws as a combo, but each series takes the chart renderer's own mark
|
|
184
|
+
rather than an authored `series[].type`. A `bar` / `line` / `area` widget that declared one series
|
|
185
|
+
as a different mark draws as its own family.
|
|
186
|
+
- A comparison overlay (`compareTo`) no longer copies its measure's authored mark or axis; there is
|
|
187
|
+
none to copy.
|
|
188
|
+
- The chart chrome in `chartConfig` (`title`, `subtitle`, `description`, `colors`, `height`,
|
|
189
|
+
`showLegend`, `showDataLabels`, `annotations`, `interaction`) is lowered exactly as before.
|
|
190
|
+
|
|
191
|
+
What to do: run `os validate` (or the spec's `os migrate meta --from 17`) on dashboards that author
|
|
192
|
+
these keys, delete them, and select `dimensions` / `values` on the widget. A chart that needs
|
|
193
|
+
per-series marks or a second value axis belongs on an `object-chart` node with inline `data`, where
|
|
194
|
+
the spec keeps `series`, `xAxis` and `yAxis` authorable.
|
|
195
|
+
|
|
196
|
+
This is released as `minor`, following this repository's version policy: breaking semantics are
|
|
197
|
+
marked `minor` and described here.
|
|
198
|
+
- 00ccdf7: The `object-pivot` registration publishes `drillDown` as an input (objectui#11440).
|
|
199
|
+
|
|
200
|
+
`ObjectPivotTable` has always read `drillDown` (whether the drill is on, its filter, title, target, columns, row cap and report), and the `pivot` node's refusal of `drillDown` names `object-pivot` as where a pivot drill is authored. The registration did not list it, so the `sdui-parser` manifest and the designer did not offer it. Its description says `drillDown.mode` does not apply: every click point on a pivot is an aggregated bucket, so the pivot always drills through. `@object-ui/types/zod`'s new `object-pivot` arm declares the same member (`.changeset/11440-arm-passing-types.md`). Nothing else about the block changes.
|
|
201
|
+
- 83e3f83: The dashboards' metric card node, `plugin-dashboard:metric`, is a declared node type, and both dashboard surfaces hand `SchemaRenderer` declared nodes with no cast (objectui#11466).
|
|
202
|
+
|
|
203
|
+
- **New export `DashboardMetricNodeSchema`:** the node `MetricWidget` renders under `plugin-dashboard:metric`, with the keys it reads off the node, typed by `MetricWidgetProps`. This package enters it in `@object-ui/types`' `CustomNodeRegistry` under that key, so a program that loads this package's typings has the node as a member of `DeclaredNode`: authorable in a node slot and at `SchemaRenderer`'s `schema` prop, with its keys checked.
|
|
204
|
+
- **`DashboardGridLayout`'s static `pivot` node** (a `pivot` widget drawn from inline rows) states the keys `PivotTableSchema` declares and `PivotTable` draws, read from `options`: `title`, `rowField`, `columnField`, `valueField`, `aggregation`, `showRowTotals`, `showColumnTotals`, `format`, `columnColors` and `className`. It used to spread `options` whole, so every option key became a key of the node, and `SchemaRenderer` handed each one on as a React prop. Now no other option key reaches the node: not a host prop of `PivotTable`'s (such as `rowLabels`), and not a `BaseSchema` node key other than `className` (such as `id`, `style` or `hidden`). Each key listed above draws as before.
|
|
205
|
+
- **The two surfaces' casts before `SchemaRenderer` are gone** (`DashboardGridLayout`'s `as BaseSchema | string | null | undefined` and `DashboardRenderer`'s `as BaseSchema`). Each surface's node builder returns `SchemaRenderer`'s own prop type, so every node is checked against its declared type where it is built. No runtime change.
|
|
206
|
+
- 83e3f83: **An `object-metric` node in a dashboard widget's legacy `component` envelope now draws the retired-format prompt instead of its number (objectui#11466).** This follows the maintainer's ruling A on objectui#11466, which extends ruling C on objectui#11525 (a dashboard metric takes its number only through a `dataset`) to this last inline metric form.
|
|
207
|
+
|
|
208
|
+
**What changes on screen.** The change covers a stored widget written in objectui's legacy envelope format, `{ id, component: { type: 'object-metric', objectName, aggregate, … }, layout }`, which the spec's widget has no member for:
|
|
209
|
+
|
|
210
|
+
- **Before:** `DashboardRenderer` and `DashboardGridLayout` drew the aggregated number in the tile, and on `DashboardRenderer` the dashboard filter bar's value was merged into the node as a flat `filter`.
|
|
211
|
+
- **Now:** both surfaces draw "This widget uses a retired data format. Edit it to bind a dataset." and send no query. The same holds when the node is written under its registration's full name, `plugin-dashboard:object-metric`. The filter bar's broadcast no longer covers `object-metric`.
|
|
212
|
+
|
|
213
|
+
Breaking for stored dashboards that still carry this form; `minor` because objectui never declares `major`. No producer of the form was measured in objectui or objectstack outside the pin that covered it (the measurement is on objectui#11466); stored customer dashboards are unmeasured, the gap ruling C accepted.
|
|
214
|
+
|
|
215
|
+
**The fix.** Replace the envelope with a dashboard widget bound to a dataset: set `dataset` and select its `values` by name, as for any metric widget. A dataset-bound metric draws its number.
|
|
216
|
+
|
|
217
|
+
**What does not move:**
|
|
218
|
+
|
|
219
|
+
- Every other node in a `component` envelope draws as before, and the filter bar still scopes an envelope's `object-chart` and `object-data-table`.
|
|
220
|
+
- An `object-metric` block authored on a page (`{ type: 'object-metric', properties: { … } }`) draws as before.
|
|
221
|
+
- The zod and TypeScript authoring faces are unchanged.
|
|
222
|
+
- f560ded: Dashboard percent faces read the storage they are told, never a storage guessed
|
|
223
|
+
from the value (objectui#11475).
|
|
224
|
+
|
|
225
|
+
- **`object-metric` over a `percent` field** renders its `sum` / `avg` / `min`
|
|
226
|
+
/ `max` at the field's own storage (`percentCellScale`, the spec's
|
|
227
|
+
`percentScaleOf`), through the list cell's `formatPercent`, the way it already
|
|
228
|
+
renders a currency. A field declaring `max: 100` that averages `50` reads
|
|
229
|
+
`50%`. The width is the authored `%` pattern's decimals, or else the
|
|
230
|
+
field's width.
|
|
231
|
+
- **`metric` with a `%` pattern** holds no field, so the pattern states the
|
|
232
|
+
storage: numeral's `%` multiplies by 100, so the value is a fraction. A ratio
|
|
233
|
+
of exactly `1` reads `100%` again. A stored value of `1` or more is
|
|
234
|
+
multiplied by 100 now, where the old guess passed it through.
|
|
235
|
+
- **`renderFieldValue`** (the data table and the record drawer) reads a
|
|
236
|
+
`percent` or `progress` field's storage from its declaration. `FieldMeta` gains
|
|
237
|
+
`max`, copied from the schema field def by `buildFieldMeta`. As a `FieldMeta`
|
|
238
|
+
member it joins `ObjectDataTable`'s read-side refusal band, so an authored
|
|
239
|
+
column cannot restate how its field stores. Any other field with a `%`
|
|
240
|
+
pattern reads its value as a fraction, numeral's reading.
|
|
241
|
+
- 8366acc: The drill-down drawer scopes a dataset-bound drill report by `runtimeFilter`, and the pre-9.0 object-bound drill report is retired (objectui#11506).
|
|
242
|
+
|
|
243
|
+
**Fixed (`@object-ui/plugin-dashboard`) — a drilled report showed the whole dataset.** The drawer opened by an `object-pivot` or `object-metric` `drillDown.report` wrote the widget's resolved filter into the report as `filter`. `ReportSchema` refuses that key and `DatasetReportRenderer` does not apply it (objectui#5137; it only warns in development), so a drill from a filtered widget queried the dataset unscoped. The drawer now writes the filter as the report's `runtimeFilter`, joined to the report's own `runtimeFilter` with `$and` when it has one, the same single join the `filter` write used. A `filter` that an unvalidated stored report still carries is not read as a scope, and it is not removed: the renderer still says in development that it was not applied.
|
|
244
|
+
|
|
245
|
+
**Changed (`@object-ui/plugin-dashboard`) — which value the drawer draws as a report.** The drawer used to treat a `report` as a report when it carried a `columns` array or an `objectName` key, both markers of the pre-9.0 object-bound form. It now treats it as a report when it is dataset-bound: a non-empty `dataset`, or a `joined` report with a block that binds one (the reading `@object-ui/plugin-report`'s `isDatasetReport` uses). A tabular, summary or joined dataset report, which carries no `columns`, is now drawn as a report; it used to list the records. A `{ name }` reference and the retired object-bound form list the records, scoped by the drill filter, as a drill with no `report` does.
|
|
246
|
+
|
|
247
|
+
BREAKING (`@object-ui/types`): `DrillDownConfig.report` narrows to the dataset-bound report. (The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own breaking changes ship as `minor` with the breaking semantics stated here.)
|
|
248
|
+
|
|
249
|
+
- FROM: `report?: { name: string; objectName: string; type?: 'tabular' | 'summary' | 'matrix' | 'joined'; columns: Array of unknown; [k: string]: unknown } | { name: string }`
|
|
250
|
+
- TO: `report?: SpecReportInput | { name: string }`, where the inline arm is `@objectstack/spec`'s `ReportSchema` author shape by reference, the same schema the `report` node's `report` member declares (objectui#11440).
|
|
251
|
+
|
|
252
|
+
The zod mirror, `DrillDownConfigSchema.report`, takes the spec's `ReportSchema` the same way, ahead of the unchanged `{ name }` reference arm, so the spec's required keys and refusals apply to an inline drill report: `name` and `label` are required, a report that is not `joined` needs a `dataset` and a non-empty `values` (measure names), a `joined` one needs non-empty `blocks`, and the inline arm refuses `filter` and `objectName`, each naming its replacement. The retired form had no producer when this change was measured (this repository, and `@objectstack`'s examples and docs), and through the real drawer it drew an empty presentation and issued no query, so it applied no filter under either key. There is no alias window. Migration: rewrite an object-bound drill report as a dataset-bound one, a `dataset` with `rows`, `values` and, for a matrix, `columns` as dimension names, and put a fixed scope in `runtimeFilter`.
|
|
253
|
+
|
|
254
|
+
Which face refuses the retired form: the TypeScript declaration does (`objectName` is a member of neither arm), and so does the strict authoring face, `StrictAnyComponentSchema`, which refuses it at the report, naming `objectName`. `objectui validate` does not: it runs `safeValidateSchema`, the tolerant face, where a value that fails the inline arm and carries a string `name` still parses as a `{ name }` reference, stripped to that member, as before, and the document is reported valid. At runtime the drawer then lists the records, as for any `{ name }` reference.
|
|
255
|
+
|
|
256
|
+
⚠️ **Dated note, 2026-10-03 — the `{ name }` reference arm is retired — objectui#11517.** At this change `DrillDownConfig.report` was `SpecReportInput | { name: string }`, the zod mirror kept the `{ name }` reference arm unchanged, and `objectui validate` read the retired object-bound form as a `{ name }` reference and reported the document valid; now, later in this same release, the reference arm is retired on the TypeScript face and both zod faces. So the TO line above reads `report?: SpecReportInput`, a bare `{ name }` is refused by name, and the tolerant face refuses the object-bound form at its `objectName`, naming this entry's retirement; "`objectName` is a member of neither arm" reads "is no report member". The drawer is unchanged: it lists the records for either form. `.changeset/11517-drill-report-name-retired.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
257
|
+
- 9d7419b: `DashboardRenderer`'s `onWidgetsReorder` hands back the slot's own array type, `DashboardComponentSchema['widgets']`, instead of `DashboardWidgetSchema[]`; the dashboard's `object-chart` producers name `ObjectChartSchema`; and a widget with no `type`, or with a `type` that names nothing, draws what the spec says it is (objectui#11514). `minor`, per this repository's version alignment: a reorder handler typed `(widgets: DashboardWidgetSchema[]) => void` stops compiling, because the component arm of a `widgets[]` entry is no longer assignable to `DashboardWidgetSchema`. Type the handler's parameter as `DashboardComponentSchema['widgets']`.
|
|
258
|
+
|
|
259
|
+
- **A widget with no `type` is a `metric` widget.** `@objectstack/spec`'s `DashboardWidget.type` defaults to `metric`; both surfaces read that default from the spec and draw the widget exactly as the same widget with `type: 'metric'`: inline, bound to a dataset (where a typeless widget with a dimension drew a bar chart), and in `DashboardRenderer`'s mobile metric row. It used to reach the slot-component passthrough and draw the registry's red "Unknown component type" (OBJUI-001) panel.
|
|
260
|
+
- **A `type` that names no family** (and no component type) draws the labelled placeholder "「type」chart type is not supported yet", as a known family with no renderer does, instead of that red panel. Both validator faces already refuse such a widget at `type`.
|
|
261
|
+
- **The slot-component passthrough** serves the slot's component arm (`metric-card`) alone, unchanged for it.
|
|
262
|
+
- **Slot-entry reads.** `DashboardRenderer`, `DashboardGridLayout`, `DashboardWithConfig` and the retired-widget detector read a `widgets[]` entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`, which is what lets `@object-ui/types` drop the component type from `DashboardWidgetSchema['type']`.
|
|
263
|
+
- **The `object-chart` producers.** A series dispatch carries the family as a literal union whose members are all families `ObjectChartSchema.chartType` declares, so the node both surfaces build for a `provider: 'object'` series widget satisfies `ObjectChartSchema` with no cast.
|
|
264
|
+
- **README.** "Reading a widget key off `widgets[]`" narrows an entry on `type` before reading a widget key, and a new section states what a widget with no `type`, or an unknown one, draws.
|
|
265
|
+
- **What does not move.** Every widget that names a known family draws as before: the dispatch routes the same families, and each `object-chart` node carries the same `chartType` it carried. A legacy `component` envelope with no `type` is not the spec's widget and is not given its default; it draws under its card heading as before, and a number or `true` in its `component` draws the same text, now forwarded through `toRenderableSchema`.
|
|
266
|
+
|
|
267
|
+
⚠️ **Dated note, 2026-10-03 — a dataset-less `provider: 'object'` metric is retired — objectui#11525.** At this change, "Every widget that names a known family draws as before" held for the single-value family's inline `provider: 'object'` widget, which drew its number through a flat `object-metric` node. Now a `metric`, `gauge`, `solid-gauge`, `kpi` or `bullet` widget, or a typeless one, with no `dataset` and an `options.data` (or widget-level `data`) of `{ provider: 'object', … }` draws the retired-format placeholder on both surfaces, by the maintainer's ruling C on objectui#11525. A typeless widget still draws exactly as the same widget with `type: 'metric'`, so it draws that placeholder too. `.changeset/11525-inline-metric-retired.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
268
|
+
|
|
269
|
+
⚠️ **Dated note, 2026-10-03 — the grid's static pivot node states its keys — objectui#11466.** At this change, "Every widget that names a known family draws as before" held for a `pivot` widget drawn from inline rows on `DashboardGridLayout`, whose node spread the widget's `options` whole. Now that node states the keys `PivotTableSchema` declares and `PivotTable` draws, read from `options`, and no other option key reaches it. `.changeset/11466-dashboard-metric-node.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
270
|
+
|
|
271
|
+
⚠️ **Dated note, 2026-10-03 — an envelope's `object-metric` draws the placeholder — objectui#11466.** At this change, a legacy `component` envelope "draws under its card heading as before" whatever node it held. Now an `object-metric` node in it draws the retired-format placeholder under that heading and sends no query, by the maintainer's ruling A on objectui#11466; every other envelope node draws as before. `.changeset/11466-envelope-object-metric-retired.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
272
|
+
- 160c6c6: **A dashboard metric widget bound inline to an object now shows the
|
|
273
|
+
retired-format prompt instead of its number (objectui#11525).** This follows
|
|
274
|
+
the maintainer's ruling C on objectui#11525.
|
|
275
|
+
|
|
276
|
+
**What changes on screen.** The change covers a stored single-value widget:
|
|
277
|
+
`metric`, `gauge`, `solid-gauge`, `kpi`, `bullet`, or a widget with no `type`,
|
|
278
|
+
which draws as `metric`. If it has no `dataset` and its `options.data` (or
|
|
279
|
+
widget-level `data`) is `{ provider: 'object', object, aggregate }`, it is
|
|
280
|
+
affected:
|
|
281
|
+
|
|
282
|
+
- **Before:** both `DashboardRenderer` and `DashboardGridLayout` aggregated the
|
|
283
|
+
object and drew the number in the tile.
|
|
284
|
+
- **Now:** the tile shows "This widget uses a retired data format. Edit it to
|
|
285
|
+
bind a dataset." and sends no query. This is the same placeholder a `pivot`
|
|
286
|
+
widget with that config already showed (objectui#10528).
|
|
287
|
+
|
|
288
|
+
Breaking for stored dashboards that still carry this form; `minor` because
|
|
289
|
+
objectui never declares `major`. Under ADR-0021 a widget binds a
|
|
290
|
+
semantic-layer `dataset`, and both validator faces already refuse a widget
|
|
291
|
+
without one, so nothing authored against the current contract is affected. No
|
|
292
|
+
example, objectui authoring surface, cloud dashboard or AI Studio path was
|
|
293
|
+
measured emitting the form (the measurements are on objectui#11525).
|
|
294
|
+
|
|
295
|
+
**The fix.** Rebind the widget to a dataset: set `dataset` and select its
|
|
296
|
+
`values` by name. A dataset-bound metric draws its number as before.
|
|
297
|
+
|
|
298
|
+
**What does not move:**
|
|
299
|
+
|
|
300
|
+
- The inline `provider: 'object'` chart and table widgets still draw.
|
|
301
|
+
- A static metric (`options.value` or an inline row array) still draws its value.
|
|
302
|
+
- The zod and TypeScript authoring faces are unchanged.
|
|
303
|
+
- An `object-metric` node an author places in a widget's legacy `component`
|
|
304
|
+
envelope still receives the dashboard filter bar's values.
|
|
305
|
+
|
|
306
|
+
The flat `object-metric` node the two surfaces used to build carried an
|
|
307
|
+
ObjectQL-dialect `filter` that no node type declares; it is gone.
|
|
308
|
+
|
|
309
|
+
⚠️ **Dated note, 2026-10-03 — the envelope's `object-metric` is retired too — objectui#11466.** At this change, "An `object-metric` node an author places in a widget's legacy `component` envelope still receives the dashboard filter bar's values" held, because `object-metric` stayed in the filter broadcast's filterable set for that node. Now, by the maintainer's ruling A on objectui#11466 (extending this change's ruling C), that envelope node draws the same retired-format placeholder on both surfaces and sends no query, and `object-metric` left the filterable set. `.changeset/11466-envelope-object-metric-retired.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
310
|
+
- 64dae8e: Six user-visible fixes across the maker surface, the assistant rail and the
|
|
311
|
+
dataset captions.
|
|
312
|
+
|
|
313
|
+
**The maker's start chips now promise only what ADR-0112 v1 builds
|
|
314
|
+
(cloud#1984).** Two of the five asked for automation the first version has no
|
|
315
|
+
flows or actions for — the ticket chip said 「状态流转」, the inventory chip said
|
|
316
|
+
「低库存预警」 — and the measured behaviour was not a refusal but a silent
|
|
317
|
+
degrade: a status kanban and a low-stock view. The chip promised an alert and
|
|
318
|
+
delivered a page. All five are reworded in all ten packs (and in the call-site
|
|
319
|
+
`defaultValue` fallbacks, which are a second copy of the same strings) to ask
|
|
320
|
+
for objects, fields, views, pages, dashboards and sample data, keeping each a
|
|
321
|
+
real business scenario — the ticket chip now asks for a status field and a board
|
|
322
|
+
grouped by it, the inventory chip for a view that filters below the reorder
|
|
323
|
+
point. A note beside the keys says to revert when v2 re-adds flows.
|
|
324
|
+
|
|
325
|
+
**Five newer AI tools get their step labels (objectui#7481).** A zh conversation
|
|
326
|
+
read `✓ Get authoring rules 已完成` between 「读取元数据结构」 and 「列出对象」:
|
|
327
|
+
`get_authoring_rules` (cloud#1837), plus `load_tools`, `open_record`,
|
|
328
|
+
`test_flow` and `toggle_flow`, are registered by the cloud AI runtime but are
|
|
329
|
+
newer than the pinned spec's tool registry, so they had no `chatbot.tool.*`
|
|
330
|
+
entry in any pack and fell through to the English title-caser.
|
|
331
|
+
|
|
332
|
+
**The assistant rail follows the thread when you send (objectui#7480).** The
|
|
333
|
+
rail and the full-page maker are the same component; what differs is width. A
|
|
334
|
+
reply that still ends on screen in the wide column runs two or three times
|
|
335
|
+
taller in a ~360px rail, so `StickToBottom`'s lock is escaped by the time the
|
|
336
|
+
user types and the new bubble, the tool steps and the streaming answer all land
|
|
337
|
+
below the fold. Every send path now re-arms the lock — including the plan-card
|
|
338
|
+
"Build it" and 确认修改 approvals, whose own code comments already named this
|
|
339
|
+
miss. Message APPENDS deliberately do not, so a user reading back through the
|
|
340
|
+
thread mid-answer is never yanked to the bottom.
|
|
341
|
+
|
|
342
|
+
**Console toasts move off the assistant composer (objectui#7482).** 「客户更新
|
|
343
|
+
成功」 sat on the ChatDock composer's send button and stayed there. One defect,
|
|
344
|
+
two symptoms: `apps/console` pinned the toaster to `bottom-right` — an override
|
|
345
|
+
that predates ADR-0057 P3a — so a toast both covered the button and, because
|
|
346
|
+
sonner pauses a toast's dismiss timer while the pointer is inside the toaster
|
|
347
|
+
region, never got to run its 4s timer with a pointer resting on the composer
|
|
348
|
+
underneath. The override is gone; the console takes `ConsoleToaster`'s own
|
|
349
|
+
documented top-right anchor, and the 4s success duration is now pinned.
|
|
350
|
+
|
|
351
|
+
**Built-in aggregate captions follow the locale everywhere (objectui#7534).**
|
|
352
|
+
objectui#7258 taught `buildChartSeries()` to resolve a server-minted default
|
|
353
|
+
measure through the locale map, so a chart legend read `计数` while the table
|
|
354
|
+
beneath it, the KPI caption, the pivot header and the dataset preview still
|
|
355
|
+
printed the server's hard-coded English `Count`. `buildDatasetFieldHelpers()`
|
|
356
|
+
takes the same optional `builtinAggregateLabels`, resolving through the one
|
|
357
|
+
`resolveMeasureLabel` order, and the five call sites pass it. Omitting the
|
|
358
|
+
argument reproduces the previous output byte for byte, and an author-declared
|
|
359
|
+
measure still keeps its own label verbatim (objectui#4106).
|
|
360
|
+
|
|
361
|
+
**The activity feed stops asking for an object the environment does not have
|
|
362
|
+
(objectui#7476).** A tenant environment has no `sys_activity`, so every page
|
|
363
|
+
load issued a request that 404'd. Everything downstream was already correct —
|
|
364
|
+
the adapter memoizes the missing collection, its logger demotes the failure, the
|
|
365
|
+
feed retires as an ANSWER and the panel renders its earned empty state — so what
|
|
366
|
+
is left is the request itself, and `data-objectstack` states the rule for it:
|
|
367
|
+
the cure for a doomed request is not issuing it. New `useObjectPresence` reads
|
|
368
|
+
the object registry the shell loads for the nav anyway; only a registry that has
|
|
369
|
+
ANSWERED and lists other objects without this one skips the read. Every
|
|
370
|
+
uncertainty — no provider, empty registry, still loading, errored — reads as
|
|
371
|
+
before, because a wrong skip would cost a real deployment its feed.
|
|
372
|
+
- 8a6ecac: Retire `FieldMeta.referenceTo` and withdraw the README line documenting it as an
|
|
373
|
+
author-facing column override (objectui#6597, enforce-or-remove — withdraw branch).
|
|
374
|
+
|
|
375
|
+
**The measurement.** The README documented `referenceTo` as an author override you could
|
|
376
|
+
pass on a `object-data-table` column to "bypass auto-detection" of a lookup's related
|
|
377
|
+
object. Two `keyof FieldMeta` populations exist, kept separate per the card's own trap
|
|
378
|
+
warning: `DatasetRelationship.referenceTo` (a resolver's *output*, unrelated) and this
|
|
379
|
+
package's `FieldMeta.referenceTo` (the card's actual subject). For the latter, neither the
|
|
380
|
+
schema-derived value nor an authored column override ever reached a reader:
|
|
381
|
+
`LookupCellRenderer` (`@object-ui/fields`) resolves its lookup target from
|
|
382
|
+
`field.reference_to` / `field.reference` — never `field.referenceTo` — and
|
|
383
|
+
`computeLookupExpand` builds `$expand` from the OBJECT SCHEMA's field types, never from an
|
|
384
|
+
authored column key. Re-measured on this branch's base (`881d5c292`) with the
|
|
385
|
+
`referenceTo`-vs-`options` positive control already in
|
|
386
|
+
`ObjectDataTable.overrideSource-6425.test.tsx`: `options` (a live override) separates two
|
|
387
|
+
equal-valued columns; `referenceTo` does not — an authored override renders
|
|
388
|
+
byte-identical to its absence.
|
|
389
|
+
|
|
390
|
+
**No authoring story survived the search either.** `ObjectGrid`'s own relational-meta
|
|
391
|
+
pass-through (`applyRelationalMeta`, `plugin-grid/src/ObjectGrid.tsx`) copies
|
|
392
|
+
`reference_to` / `reference` / `display_field` / etc. from the SCHEMA field def only, at
|
|
393
|
+
all three of its call sites — never from an authored column override. No doc, example, or
|
|
394
|
+
fixture in this repo shows a table column pinning a lookup's target away from what its
|
|
395
|
+
schema field already says. Under the maintainer's standing startup-stage rule
|
|
396
|
+
(2026-08-27: deprecated/alias spellings retire immediately, no transition windows), no
|
|
397
|
+
measured demand selects withdraw.
|
|
398
|
+
|
|
399
|
+
**Both `keyof FieldMeta` seam bands, both in scope.** `ObjectDataTable` derives two
|
|
400
|
+
refusal bands from `keyof FieldMeta` — `EnrichedColumn`'s write-side tombstones
|
|
401
|
+
(objectui#6373) and `AuthoredColumnOverrides`' read-side band (objectui#6425) — so
|
|
402
|
+
deleting the member would have dropped `referenceTo` from both as a side effect, silently
|
|
403
|
+
un-enforcing objectui#6425's "not declared as spelled, still HELD" verdict. A new
|
|
404
|
+
hand-written `ObjectDataTableRetiredReferenceToTombstone` (`{ referenceTo?: never }`) is
|
|
405
|
+
intersected into both halves of the seam, the exact sibling of
|
|
406
|
+
`ObjectDataTableRetiredDecimalsTombstone` (objectui#6625) — same mechanism, same reason.
|
|
407
|
+
`ObjectDataTableColumnHolds` — the interface that carried the HELD verdict — is now empty
|
|
408
|
+
(kept, not deleted, as the documented extension point a future ruling holds a new key
|
|
409
|
+
onto).
|
|
410
|
+
|
|
411
|
+
**Ablation.** Removing the tombstone intersection (replacing it with the old
|
|
412
|
+
`{ referenceTo?: unknown }` HELD shape) turns two `@ts-expect-error` directives unused
|
|
413
|
+
(TS2578) — `tsc -p tsconfig.test.json` exits 2. Restoring the intersection returns a clean
|
|
414
|
+
exit 0. This proves the tombstone, not the derived band or a lingering hold, is what
|
|
415
|
+
refuses the key now.
|
|
416
|
+
|
|
417
|
+
**Behaviour is unchanged** — pinned by the unchanged `referenceTo reaches NOTHING on this
|
|
418
|
+
path` runtime assertion, and by two new counter-control tests (mirroring the ones
|
|
419
|
+
objectui#6625 added for `decimals`) proving the tombstone specifically is what refuses the
|
|
420
|
+
key at both seam bands.
|
|
421
|
+
|
|
422
|
+
Marked `minor` per this repo's version-alignment rule (AGENTS.md 版本号策略), which
|
|
423
|
+
reserves `major` for following `@objectstack` across a major. Scope note, measured rather
|
|
424
|
+
than assumed: `FieldMeta`, `AuthoredColumnOverrides`, `EnrichedColumn` and
|
|
425
|
+
`ObjectDataTableColumnHolds` are absent from `dist/index.d.ts` — `plugin-dashboard`'s
|
|
426
|
+
barrel re-exports only the `ObjectDataTable` component, and the package's `exports` map
|
|
427
|
+
publishes only `"."`. No downstream type moves; this is a package-internal contract change
|
|
428
|
+
plus a README correction, not a removal from a published type surface.
|
|
429
|
+
|
|
430
|
+
⚠️ **Dated note, 2026-09-30 — `reference_to` has since left the cell and the grid's copy — objectui#11070.** The measurement above read `LookupCellRenderer` resolving its target from `field.reference_to` / `field.reference`, and `applyRelationalMeta` copying `reference_to`. Later in this same release objectui#11070 (round 4) narrowed the cell to `reference` alone and dropped `reference_to` from both copy sets. The verdict on `referenceTo` is unchanged. `.changeset/11070-reference-to-round4.md` states what ships; the text above is kept as the reading of this change.
|
|
431
|
+
- 891a188: Retire `FieldMeta.decimals` and the now-unfed `BuildFieldMetaParams.overrides.decimals`
|
|
432
|
+
from `plugin-dashboard`'s shared field-rendering helpers (objectui#6625,
|
|
433
|
+
enforce-or-remove).
|
|
434
|
+
|
|
435
|
+
`buildFieldMeta` computed `decimals: overrides.decimals ?? meta?.decimals ?? meta?.scale`
|
|
436
|
+
on **every** call and the value **reached nothing**. Re-measured on this branch's base
|
|
437
|
+
(`efdc6c62`): **zero `.decimals` member reads** across `@object-ui/fields`,
|
|
438
|
+
`@object-ui/i18n`, `@object-ui/components`, `@object-ui/core` and `plugin-dashboard`
|
|
439
|
+
itself — the only non-comment occurrence was the write being removed here. The
|
|
440
|
+
**positive control in the same query shape** fires: `.scale` member reads hit
|
|
441
|
+
`NumberField.tsx`, `GridField.tsx` and `fields/src/index.tsx`. So the zero is a
|
|
442
|
+
finding, not a broken query. The `overrides.decimals ??` head of that chain had already
|
|
443
|
+
lost its only feeder when objectui#6425's ruling removed the authored read from
|
|
444
|
+
`ObjectDataTable.enrich()`; `RecordDetailDrawer`, the only other `buildFieldMeta`
|
|
445
|
+
caller, passes no overrides at all. Both halves retire together, so the key leaves in
|
|
446
|
+
one move.
|
|
447
|
+
|
|
448
|
+
Behaviour is unchanged — no reader existed to notice, pinned by the unchanged runtime
|
|
449
|
+
assertion in `ObjectDataTable.overrideSource-6425.test.tsx` that an authored `decimals`
|
|
450
|
+
renders byte-identical to its absence.
|
|
451
|
+
|
|
452
|
+
**The refusal did NOT leave with the member.** `ObjectDataTable` derives *two* refusal
|
|
453
|
+
bands from `keyof FieldMeta` — `EnrichedColumn`'s write-side tombstones (objectui#6373)
|
|
454
|
+
and `AuthoredColumnOverrides`' read-side band (objectui#6425) — so deleting the member
|
|
455
|
+
would have dropped `decimals` from both as a side effect, silently un-enforcing
|
|
456
|
+
objectui#6425's retire. A new hand-written `ObjectDataTableRetiredDecimalsTombstone`
|
|
457
|
+
(`{ decimals?: never }`) is intersected into both halves of the seam, the same shape and
|
|
458
|
+
for the same reason as `ObjectGrid`'s `ObjectGridRetiredOptionsTombstone`. The verdict
|
|
459
|
+
is unchanged since 2026-08-27; only the artefact enforcing it moved, from derived to
|
|
460
|
+
hand-written.
|
|
461
|
+
|
|
462
|
+
Marked `minor` per this repo's version-alignment rule (AGENTS.md 版本号策略), which
|
|
463
|
+
reserves `major` for following `@objectstack` across a major. Scope note, measured rather
|
|
464
|
+
than assumed: `FieldMeta`, `BuildFieldMetaParams`, `AuthoredColumnOverrides` and
|
|
465
|
+
`EnrichedColumn` are **absent from `dist/index.d.ts`** — they are not re-exported by the
|
|
466
|
+
package barrel, and the `exports` map publishes only `"."`, so no consumer can name them.
|
|
467
|
+
No downstream type moves; this is a package-internal contract change.
|
|
468
|
+
- 220c18d: Dashboard/analytics widgets get a self-explaining DEFAULT empty state, stated
|
|
469
|
+
once for the surface (objectui#7063).
|
|
470
|
+
|
|
471
|
+
Maintainer ruling 2026-08-31 (hotcrm#1212, following hotcrm#1203): a widget that
|
|
472
|
+
renders a bare row-placeholder on an empty result is the PLATFORM's defect and
|
|
473
|
+
must be fixed uniformly — apps must not compensate widget by widget
|
|
474
|
+
(objectstack#13848). The measured scenario is a fresh flagship-demo install:
|
|
475
|
+
eleven populated tiles and one reading exactly `暂无数据行` mid-page, which reads
|
|
476
|
+
as "the dashboard failed to load" even though the widget, its declaration and
|
|
477
|
+
its (not yet produced) data are all legitimate.
|
|
478
|
+
|
|
479
|
+
- New `WidgetEmptyState` is the seam the three dashboard surfaces now share.
|
|
480
|
+
There was no shared placeholder to fix: `DatasetWidget` wrote
|
|
481
|
+
`dashboard.noRows`, while `ObjectDataTable` and `PivotTable` wrote
|
|
482
|
+
`dashboard.noDataAvailable` — three renders, two strings, no common code.
|
|
483
|
+
- The default now reads as a STATE, not a failure: `role="status"` (the empty
|
|
484
|
+
branches previously carried no role at all, while the failure branches beside
|
|
485
|
+
them are `role="alert"`), muted treatment with an inbox glyph rather than a
|
|
486
|
+
warning triangle, and a title plus an explanation where the placeholder was a
|
|
487
|
+
single terse fragment.
|
|
488
|
+
- It names WHAT is empty with zero authored copy — the widget's data source,
|
|
489
|
+
which is the half the reader cannot already see (the tile's title is rendered
|
|
490
|
+
by the card header directly above). That is `widget.dataset` on the dataset
|
|
491
|
+
path and `schema.objectName` on the object-bound table/pivot; `PivotTable`
|
|
492
|
+
takes it as a new optional `sourceLabel` prop, which `ObjectPivotTable`
|
|
493
|
+
forwards.
|
|
494
|
+
- Copy is platform i18n: `dashboard.empty.title` / `.message` / `.sourceLabel`
|
|
495
|
+
added to `en` and all nine sibling packs. No inline `defaultValue` and no
|
|
496
|
+
interpolation — the source renders as a labelled value, so no separator is
|
|
497
|
+
concatenated in code and every pack spells its own punctuation.
|
|
498
|
+
|
|
499
|
+
No new authoring obligation and no new spec key. Note that the `emptyState`
|
|
500
|
+
override the card assumes for this surface does not exist: `emptyState` is a
|
|
501
|
+
LIST-view contract, and `@objectstack/spec`'s `DashboardWidgetSchema` declares no
|
|
502
|
+
such key — so there is nothing here for an author to override, and adding one
|
|
503
|
+
would be a contract question rather than a rider.
|
|
504
|
+
- 2ceb43a: Retire the widget `dataProvider` key. It was declared and written, but nothing
|
|
505
|
+
read it (objectui#7353, ADR-0049 remove arm).
|
|
506
|
+
|
|
507
|
+
Both dashboard surfaces build a node for the table and pivot widgets from a
|
|
508
|
+
`provider: 'object'` widget. They copied the whole provider config onto that node
|
|
509
|
+
as `dataProvider`, next to `objectName`. The widgets read only `objectName`, so
|
|
510
|
+
`dataProvider` repeated the same information in a second place that no code used.
|
|
511
|
+
|
|
512
|
+
**`@object-ui/plugin-dashboard` — BREAKING (declared `minor` per the repo's
|
|
513
|
+
version policy).** Compared with the released 17.6.0:
|
|
514
|
+
|
|
515
|
+
- `DashboardRenderer` no longer writes `dataProvider` on the `object-data-table`
|
|
516
|
+
node, and `DashboardGridLayout` no longer writes it on any node it builds for a
|
|
517
|
+
`provider: 'object'` widget. Since objectui#10528 the grid builds that same
|
|
518
|
+
self-fetching `object-data-table` node for such a table widget, and shows the
|
|
519
|
+
retired-widget placeholder for such a pivot widget. The `object-data-table`
|
|
520
|
+
node still writes `objectName` on both surfaces, and the object-backed table
|
|
521
|
+
widget fetches through it.
|
|
522
|
+
- In 17.6.0, the `schema` prop types of `ObjectPivotTable` and `ObjectDataTable`
|
|
523
|
+
both declare `dataProvider?: { provider: string; object?: string }`. Both now
|
|
524
|
+
declare it as a retirement tombstone (`dataProvider?: never`). TypeScript code
|
|
525
|
+
that sets `dataProvider` on these props no longer compiles.
|
|
526
|
+
- To migrate, delete `dataProvider` and set `objectName` to the object the widget
|
|
527
|
+
should load. `ObjectPivotTable`'s node has no zod schema, so for that widget
|
|
528
|
+
the type check is the only place the key is refused.
|
|
529
|
+
|
|
530
|
+
**`@object-ui/types`.** Released 17.6.0 declares neither `ObjectDataTableSchema`
|
|
531
|
+
nor `dataProvider`, so this part changes no released declaration and no released
|
|
532
|
+
validator's verdict. `ObjectDataTableSchema` itself is new after 17.6.0
|
|
533
|
+
(objectui#6576). With this change it ships with `dataProvider` retired on both
|
|
534
|
+
faces:
|
|
535
|
+
|
|
536
|
+
- the TS interface declares `dataProvider?: never`;
|
|
537
|
+
- the zod mirror (`ObjectDataTableSchema` in `@object-ui/types/zod`) refuses the
|
|
538
|
+
key by name, with a message that says to write `objectName`.
|
|
539
|
+
|
|
540
|
+
The refusal applies to any value, well-formed or malformed. Other undeclared keys
|
|
541
|
+
are still accepted, as they are on every `BaseSchema` node.
|
|
542
|
+
|
|
543
|
+
**Why a tombstone rather than a plain deletion.** Both of these types extend
|
|
544
|
+
`BaseSchema`. Its TypeScript index signature and its `.passthrough()` zod schema
|
|
545
|
+
accept any key that is not declared. A deleted member would therefore still
|
|
546
|
+
compile and still parse, and it would still do nothing. The tombstone turns that
|
|
547
|
+
silent no-op into a named refusal that points at `objectName`. It reads nothing,
|
|
548
|
+
so no reader was added.
|
|
549
|
+
|
|
550
|
+
**DOM.** All three producer paths were rendered for real before the change. The
|
|
551
|
+
key never reached a DOM attribute: there was no `dataprovider` or
|
|
552
|
+
`data-provider` attribute and no React unknown-prop warning. So this change does
|
|
553
|
+
not close any objectui#4357-class leak.
|
|
554
|
+
- 0dc2c93: `compareTo` on a `scatter` chart is no longer supported — scatter joins pie / donut /
|
|
555
|
+
funnel on the list of chart families that ignore it (objectui#7402, maintainer ruling
|
|
556
|
+
2026-09-03).
|
|
557
|
+
|
|
558
|
+
**This removes a published capability, deliberately.** Until now a `chartType: 'scatter'`
|
|
559
|
+
chart (and the dashboard widget types `scatter` and `bubble`, which both render as one)
|
|
560
|
+
with `compareTo` set synthesised a muted "previous period" overlay series. It drew the
|
|
561
|
+
wrong picture: a scatter binds ONE measure, and the renderer reads y through the single
|
|
562
|
+
`YAxis dataKey={series[0].dataKey}`, so the overlay was plotted on the PRIMARY series' y
|
|
563
|
+
— "previous period" painted exactly on top of "current" (objectui#7194).
|
|
564
|
+
|
|
565
|
+
Enforce-or-remove: rather than keep drawing that, the capability is removed until it can
|
|
566
|
+
be drawn honestly. Drawing a real second measure on a scatter needs the multi-measure
|
|
567
|
+
projection recorded as option A of objectui#7194, which is not built (zero authored
|
|
568
|
+
callers). **If and when that projection lands, `compareTo` on a scatter returns with
|
|
569
|
+
it** — it is the same missing mechanism, one payment.
|
|
570
|
+
|
|
571
|
+
What changes for authors:
|
|
572
|
+
|
|
573
|
+
- A `compareTo` on a scatter is now IGNORED rather than drawn. The primary series still
|
|
574
|
+
renders exactly as before — nothing refuses, nothing goes blank, and no comparison
|
|
575
|
+
query is issued on the inline chart path.
|
|
576
|
+
- No `<measure>__comparison` (inline chart) / `<measure>__compare` (dashboard) series is
|
|
577
|
+
appended for a scatter, so a compare-to scatter document also never reaches the
|
|
578
|
+
two-or-more-series scatter refusal being added under objectui#7194.
|
|
579
|
+
- Charts that keep the overlay: line, area, bar, horizontal-bar, combo. Charts that
|
|
580
|
+
ignore `compareTo`: pie, donut, funnel and — as of this change — scatter (and the
|
|
581
|
+
`bubble` widget type that renders as a scatter).
|
|
582
|
+
|
|
583
|
+
Reachability at the time of the change: **0** authored scatter/bubble instances in-repo
|
|
584
|
+
across both spellings (control `"type": "bar"` fires at 5 example files); incidence in
|
|
585
|
+
deployed tenant metadata is not measurable from this repo.
|
|
586
|
+
- 721d1e0: One declaration of which chart families ignore `compareTo` (objectui#7495).
|
|
587
|
+
|
|
588
|
+
**New in `@object-ui/core`:** `chartTypeIgnoresCompareTo(chartType)`, exported from
|
|
589
|
+
`chart-presentation`. It answers `true` for `pie`, `donut`, `funnel` and `scatter` and
|
|
590
|
+
`false` for every other chart family, an unknown string, and `undefined` — the answers
|
|
591
|
+
`@object-ui/plugin-charts`' ObjectChart already gave through its own local predicate.
|
|
592
|
+
ObjectChart now reads the core predicate instead of keeping that list, so the inline chart
|
|
593
|
+
path behaves exactly as before.
|
|
594
|
+
|
|
595
|
+
**Behaviour change in `@object-ui/plugin-dashboard`:** a dataset chart widget whose chart
|
|
596
|
+
family ignores `compareTo` no longer asks the executor for a comparison. That covers the
|
|
597
|
+
widget types `pie`, `donut`, `funnel`, `pyramid` (renders as a funnel), `scatter` and
|
|
598
|
+
`bubble` (renders as a scatter). The dashboard used to carry its own, narrower copy of the
|
|
599
|
+
list (scatter only), and it gated only the overlay series, never the query: every one of
|
|
600
|
+
these widgets still forwarded `compareTo`, still had its date window lowered into
|
|
601
|
+
`timeDimensions`, and so made the executor run the comparison pass and attach
|
|
602
|
+
`MEASURE__compare` columns. A pie, donut or funnel then appended an overlay series the
|
|
603
|
+
renderer dropped; a scatter discarded the columns. Those widgets now send the same selection as the same
|
|
604
|
+
widget without `compareTo`: one pass on the server instead of two, and nothing on screen
|
|
605
|
+
changes for a widget whose filter carries one bounded date window. One case does change on
|
|
606
|
+
screen, for the better: the dataset executor refuses a `compareTo` it has no single dated
|
|
607
|
+
window to shift, and the widget showed that refusal in place of the chart — a compare-to
|
|
608
|
+
pie, donut, funnel or scatter widget with no dated window now draws its chart, since no
|
|
609
|
+
comparison is asked for.
|
|
610
|
+
|
|
611
|
+
Unchanged: line / area / bar / horizontal-bar / combo chart widgets keep their comparison
|
|
612
|
+
overlay, and metric, gauge, table and pivot widgets keep their comparison. The gate is the
|
|
613
|
+
chart branch, not the widget type, so a `pie` widget with no dimensions — which renders as
|
|
614
|
+
a metric tile and shows the comparison as a delta — keeps it too.
|
|
615
|
+
|
|
616
|
+
Reachability at the time of the change: `compareTo` appears in no example app metadata in
|
|
617
|
+
this repository; incidence in deployed tenant metadata is not measurable from here.
|
|
618
|
+
- 1cca678: Retire the dashboard-**root** `title` read across all five surfaces (objectui#7509,
|
|
619
|
+
maintainer ruling 2026-09-04, decision batch #29, option C, under ADR-0049).
|
|
620
|
+
|
|
621
|
+
**What changes for an operator.** A stored dashboard whose header came from a legacy
|
|
622
|
+
root `title` now shows its `label`. `label` is the only header source, then the raw
|
|
623
|
+
`name`.
|
|
624
|
+
|
|
625
|
+
Per surface:
|
|
626
|
+
|
|
627
|
+
- Console dashboard page (`DashboardView`) — header falls to `label`, then `name`.
|
|
628
|
+
- Standalone dashboard embed (`DashboardRenderer`) — `header` shows `label`; a document
|
|
629
|
+
with no `label` now shows no header title at all.
|
|
630
|
+
- The `dashboard-grid` SDUI component (`DashboardGridLayout`) — heading falls to
|
|
631
|
+
`label`, then the generic `Dashboard`.
|
|
632
|
+
- Studio dashboard designer (`DashboardEditor` preview panel, `DashboardDesignPage`
|
|
633
|
+
heading) — both fall to `label`, then `name` / the generic heading.
|
|
634
|
+
|
|
635
|
+
**Why now.** `@objectstack/spec`'s `DashboardSchema` refuses a root `title` **by name**
|
|
636
|
+
(`unrecognized_keys(title)`), and the save route answers `422 INVALID_METADATA` — so no
|
|
637
|
+
authored dashboard can acquire the key, and what retires is compatibility with documents
|
|
638
|
+
stored before that refusal existed. Until now five surfaces read the legacy spelling
|
|
639
|
+
independently, which meant a legacy document could show one header in the console and a
|
|
640
|
+
different one in the designer. One spelling now answers everywhere.
|
|
641
|
+
|
|
642
|
+
**Migration.** `label` is REQUIRED on `DashboardSchema`, so a spec-valid stored dashboard
|
|
643
|
+
already carries it and needs no change — it simply starts showing that `label` instead of
|
|
644
|
+
the legacy `title`. A document carrying `title` and no `label` was already invalid; give
|
|
645
|
+
it a `label`. No in-repo document needed migrating: a sweep of all 627 tracked JSON found
|
|
646
|
+
9 dashboard-shaped nodes, and the 6 carrying a root `title` are `type: 'dashboard'`
|
|
647
|
+
component examples that declare no `header`, so none of them rendered a header title
|
|
648
|
+
either before or after.
|
|
649
|
+
|
|
650
|
+
**Not affected: widget titles.** `DashboardWidget.title` is a different, spec-**declared**
|
|
651
|
+
key (the spec's `I18nLabel`) on a different receiver, and is untouched — widget headings,
|
|
652
|
+
the designer's widget-title input and its per-locale write path all behave exactly as
|
|
653
|
+
before. Root and widget arms were separated by receiver, and the retirement's pins carry
|
|
654
|
+
widget-level controls on every surface for that reason.
|
|
655
|
+
|
|
656
|
+
⚠️ **Dated note, 2026-10-02 — the `dashboard-grid` node key is retired — objectui#10859.**
|
|
657
|
+
Later in this same release objectui#10859 batch 8 (phase 2b) unregistered the `dashboard-grid`
|
|
658
|
+
node key, so `DashboardGridLayout` is no longer an SDUI component; the heading rule above holds
|
|
659
|
+
for it wherever a host mounts it directly. The rest of this entry is kept as the reading of this
|
|
660
|
+
change.
|
|
661
|
+
- edea22a: **BREAKING** — `SchemaRendererProvider`'s `dataSource` prop, and the context
|
|
662
|
+
type every `useSchemaContext()` consumer reads back, are the published
|
|
663
|
+
`DataSource` contract instead of `any`.
|
|
664
|
+
|
|
665
|
+
**FROM** `dataSource={anything}` **TO** `dataSource={adapter}` — a `DataSource`
|
|
666
|
+
from `@object-ui/types`, or `null` / `undefined` when the host has no adapter
|
|
667
|
+
bound.
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
// before — compiled, and failed at runtime on the first find()
|
|
671
|
+
<SchemaRendererProvider dataSource={'not-an-adapter'}>
|
|
672
|
+
// before — compiled, and no reader can do anything with it
|
|
673
|
+
<SchemaRendererProvider dataSource={{}}>
|
|
674
|
+
// after
|
|
675
|
+
<SchemaRendererProvider dataSource={adapter}> // a DataSource
|
|
676
|
+
<SchemaRendererProvider dataSource={undefined}> // "I have no adapter"
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Both sites are typed `DataSource | null | undefined` — the spelling
|
|
680
|
+
`useSettledSchema` in this same package already used. The two absences are part
|
|
681
|
+
of the contract, not a weakening of it: a Studio preview, a `kind:'react'` page
|
|
682
|
+
rendered before the host's adapter connects, and a widget probe driving
|
|
683
|
+
`apiFetch` alone all render with nothing bound, and every reader in the tree
|
|
684
|
+
already guards for it. What the union refuses is everything that is not an
|
|
685
|
+
adapter: a string, an empty object, a plain data bag, a partial adapter missing
|
|
686
|
+
a required member.
|
|
687
|
+
|
|
688
|
+
The measured cost, both halves, because the two `any`s have different blast
|
|
689
|
+
radii (measured separately on `origin/main`, whole-repo type-check over the 33
|
|
690
|
+
packages that depend on `@object-ui/react`):
|
|
691
|
+
|
|
692
|
+
- the **context type** — the `any` that reaches every `useSchemaContext()`
|
|
693
|
+
reader — reds **7 diagnostics at 7 sites in 4 packages**, all of them
|
|
694
|
+
production code or a mocked module factory.
|
|
695
|
+
- the **provider prop** — the injection points — reds **52 diagnostics at 27
|
|
696
|
+
sites in 11 packages**, all but one of them test doubles.
|
|
697
|
+
|
|
698
|
+
That ordering is the reverse of the prediction on the card: the context `any`
|
|
699
|
+
was expected to be the expensive one because it infects the whole tree, and it
|
|
700
|
+
is the cheap one, because every reader in the tree already guarded and none of
|
|
701
|
+
them ever reached past `find` / `getObjectSchema`. The prop is the expensive
|
|
702
|
+
one, because the injection points are overwhelmingly test doubles that were
|
|
703
|
+
never complete adapters. The full accounting is on objectui#7912.
|
|
704
|
+
|
|
705
|
+
Two runtime behaviours change, both in the "no adapter" direction and both
|
|
706
|
+
strictly closer to what the surrounding code already intended:
|
|
707
|
+
|
|
708
|
+
- `@object-ui/components`' `kind:'react'` page passed an empty object as its
|
|
709
|
+
"no adapter yet" stand-in. An empty object is TRUTHY, so it walked past every
|
|
710
|
+
`if (!dataSource)` guard written to catch exactly that state and failed later,
|
|
711
|
+
at the call. It now passes the absent adapter itself, so the guard fires where
|
|
712
|
+
it was meant to. The module-constant identity that stand-in existed for is
|
|
713
|
+
preserved: `null` is a primitive, so the provider's memo is unaffected.
|
|
714
|
+
- `@object-ui/plugin-calendar`, `@object-ui/plugin-gantt` and
|
|
715
|
+
`@object-ui/plugin-kanban` collapse a `null` adapter from the context to
|
|
716
|
+
`undefined` before handing it to their widget, whose prop declares the single
|
|
717
|
+
spelling `dataSource?: DataSource`.
|
|
718
|
+
|
|
719
|
+
Nothing else moves at runtime: no value flowing through this key changes, and
|
|
720
|
+
no data path is touched. A TypeScript consumer outside this repo that handed
|
|
721
|
+
this prop something other than an adapter now gets a compile error naming the
|
|
722
|
+
key (TS2322 / TS2739 / TS2740), which is why the FROM/TO is spelled out above.
|
|
723
|
+
|
|
724
|
+
Five `as any` reads of this context in `@object-ui/fields` are gone — they were
|
|
725
|
+
redundant the moment the seam became honest — and `LookupField`'s local
|
|
726
|
+
re-declaration of the imported context as a `Context` of `any`, which laundered
|
|
727
|
+
its `dataSource` read while looking typed, is gone with them. Both directions of
|
|
728
|
+
the contract are pinned against the real compiler in
|
|
729
|
+
`SchemaRendererContext.dataSourceType.pin.test.ts`, and the card's planted
|
|
730
|
+
documentation probe (a bare string in `packages/react/README.md`'s provider
|
|
731
|
+
example) now fails `pnpm check:doc-snippets`, where it used to exit 0 with zero
|
|
732
|
+
diagnostics.
|
|
733
|
+
|
|
734
|
+
objectui#7912.
|
|
735
|
+
- 0ea7054: Remove 37 runtime dependencies that no file in the declaring package consumes, and gate
|
|
736
|
+
the direction so the next one cannot land (objectui#8198).
|
|
737
|
+
|
|
738
|
+
`check:phantom-deps` judges imports that are not declared; nothing judged the reverse,
|
|
739
|
+
so a declaration could outlive its last consumer indefinitely. That is what happened to
|
|
740
|
+
`recharts` in `@object-ui/components` after objectui#7397 deleted its only importer — it
|
|
741
|
+
was removed by hand on objectui#7625, and nothing would have reported the next one. The
|
|
742
|
+
new `pnpm check:unused-deps` asks the reverse question over `dependencies` and
|
|
743
|
+
`optionalDependencies` of every released package.
|
|
744
|
+
|
|
745
|
+
**Potentially breaking, for consumers relying on hoisting.** Nothing these packages ship
|
|
746
|
+
changes: their Vite `external` predicates are path-based and never read `dependencies`,
|
|
747
|
+
so no built artifact moves. What changes is the install graph — a project that imports
|
|
748
|
+
one of the removed packages while depending only on the ObjectUI package that used to
|
|
749
|
+
drag it in will no longer resolve it. Declare it directly; that is the correct
|
|
750
|
+
dependency edge in either case. The removals, by package:
|
|
751
|
+
|
|
752
|
+
- `@object-ui/plugin-designer`: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`, `@object-ui/fields`
|
|
753
|
+
- `@object-ui/plugin-chatbot`: `react-markdown`, `react-syntax-highlighter`, `remark-gfm` (and the orphaned `@types/react-syntax-highlighter`)
|
|
754
|
+
- `@object-ui/plugin-report`: `@object-ui/plugin-grid`, `clsx`, `react-i18next`, `tailwind-merge`
|
|
755
|
+
- `@object-ui/plugin-map`: `@objectstack/spec`, `lucide-react`, `zod`
|
|
756
|
+
- `@object-ui/runner`: `class-variance-authority`, `clsx`, `tailwind-merge`
|
|
757
|
+
- `@object-ui/core`: `lodash`, `zod`
|
|
758
|
+
- `@object-ui/layout`: `clsx`, `tailwind-merge`, and `react-dom` — which it pinned at an exact version in `dependencies` while also declaring it as a peer range, i.e. a library hard-depending on the renderer it asks its host to supply
|
|
759
|
+
- `@object-ui/plugin-dashboard`: `clsx`, `tailwind-merge`, and the same `react-dom` defect
|
|
760
|
+
- `@object-ui/plugin-ai`: `@object-ui/react`, `clsx`, `tailwind-merge`
|
|
761
|
+
- `@object-ui/fields`: `clsx`, `tailwind-merge`
|
|
762
|
+
- `@object-ui/console`: `@object-ui/react-runtime`, `sucrase`
|
|
763
|
+
- `@object-ui/auth`: `@object-ui/types`
|
|
764
|
+
- `@object-ui/plugin-calendar`: `@object-ui/fields`
|
|
765
|
+
- `@object-ui/plugin-editor`, `@object-ui/plugin-markdown`: `@object-ui/react`
|
|
766
|
+
- `@object-ui/react`: `react-hook-form`
|
|
767
|
+
|
|
768
|
+
Every one was verified by a whole-package grep before removal — the name appeared nowhere
|
|
769
|
+
under the package but its own manifest and CHANGELOG — and the whole workspace builds,
|
|
770
|
+
type-checks and tests green afterwards.
|
|
771
|
+
- 270f282: Six hand-rolled em-dash placeholders now draw the shared `EmptyValue` from
|
|
772
|
+
`@object-ui/components` (objectui#8504), closing the *no accessible name* half
|
|
773
|
+
of the class objectui#8491 / PR #8503 opened.
|
|
774
|
+
|
|
775
|
+
**The accessibility defect.** Each site built its own `<span>` holding a bare em
|
|
776
|
+
dash. `EmptyValue` carries three things none of them had: a `data-slot` of
|
|
777
|
+
`empty-value`, an `aria-label` resolved through the i18n label hook, and
|
|
778
|
+
`select-none` / `no-underline` / `pointer-events-none`. So a screen-reader user
|
|
779
|
+
reaching one of these cells heard a naked punctuation mark, while a neighbouring
|
|
780
|
+
cell drawn by a type-aware renderer was announced as "No value". Two of the
|
|
781
|
+
sites make the inconsistency reachable inside one surface: the metadata list
|
|
782
|
+
renders column 0's placeholder *inside the row's `<Link>`*, where the
|
|
783
|
+
hand-rolled span inherited the link colour and stayed selectable, and the
|
|
784
|
+
dashboard record drawer sits next to renderers that already returned the shared
|
|
785
|
+
component.
|
|
786
|
+
|
|
787
|
+
The six: the metadata list's `defaultCell` and the Audit tab's lock column
|
|
788
|
+
(`@object-ui/app-shell`), the dashboard record drawer's empty `<dd>`
|
|
789
|
+
(`@object-ui/plugin-dashboard`), the import wizard's saved-mapping transform
|
|
790
|
+
cell (`@object-ui/plugin-grid`), the AI-approvals `JsonBlock`
|
|
791
|
+
(`@object-ui/plugin-chatbot`), and the Public Forms object column
|
|
792
|
+
(`@object-ui/console`).
|
|
793
|
+
|
|
794
|
+
**A deliberate visual change, not a no-op.** Five sites drop
|
|
795
|
+
`text-muted-foreground` (or `/60`) for the shared `text-muted-foreground/50`, so
|
|
796
|
+
every placeholder in the workspace is now one colour. The glyph is unchanged
|
|
797
|
+
everywhere. The sixth, `JsonBlock`, keeps its `text-xs` through `className`
|
|
798
|
+
because it stands where a `text-xs <pre>` would and has no shared neighbour to
|
|
799
|
+
match — its delta is the accessible name and the three affordances only.
|
|
800
|
+
|
|
801
|
+
**Two adjacent lines in the same file, taken deliberately.** The AI-approvals
|
|
802
|
+
drawer's `proposed_by` / `decided_by` fields fell back to a bare `'—'` text node
|
|
803
|
+
inside a plain `<div>` — a different source spelling of the same rendered
|
|
804
|
+
defect, individually verified on the card rather than swept up. They are
|
|
805
|
+
converted too. `formatRelative`'s `if (!s) return '—'` is not: that helper is
|
|
806
|
+
declared `: string`, so its fallback is not a node.
|
|
807
|
+
|
|
808
|
+
Filled values are untouched in every path.
|
|
809
|
+
- 0a174f3: fix(plugin-dashboard,core,plugin-charts): route a structured `aggregate.groupBy` on `object-metric` to the spec-shape wire
|
|
810
|
+
|
|
811
|
+
An authored `aggregate.groupBy` is a union — a bare field name, or the
|
|
812
|
+
structured date-bucketing node `{ field, dateGranularity?, alias? }` — and the
|
|
813
|
+
two need different queries. The spec-shape
|
|
814
|
+
`{ groupBy: GroupByNode[], aggregations, where }` reaches `engine.aggregate` and
|
|
815
|
+
runs the server-side date-bucket engine; the legacy
|
|
816
|
+
`{ field, function, groupBy, filter }` query reaches the cube/analytics wire,
|
|
817
|
+
whose `dimensions` the contract declares as an array of dimension NAMES and
|
|
818
|
+
which does not honour `dateGranularity` at all.
|
|
819
|
+
|
|
820
|
+
`ObjectChart.runAggregate` has routed between the two since objectui#7946.
|
|
821
|
+
`ObjectMetricWidget.computeOne` had no such branch: it forwarded the authored
|
|
822
|
+
value straight through, so a metric widget carrying a structured node posted
|
|
823
|
+
`dimensions: [{ field: 'closed_at', dateGranularity: 'month' }]` — an object
|
|
824
|
+
where a name is declared. Nothing refused it and nothing reported it, so the
|
|
825
|
+
author asked for one question and the platform answered another (objectui#8613).
|
|
826
|
+
|
|
827
|
+
- The routing test and the payload are now one function,
|
|
828
|
+
`objectAggregateSpecQuery` (with `isStructuredGroupBy`) in `@object-ui/core`.
|
|
829
|
+
`ObjectChart.runAggregate` and `ObjectMetricWidget.computeOne` both call it,
|
|
830
|
+
so the two renderers cannot post different wires for one authored shape. The
|
|
831
|
+
measure alias is `chartMeasureKey`'s answer, i.e. what the chart's own
|
|
832
|
+
`aggregateValueKey` already delegated to — the chart's posted payload is
|
|
833
|
+
unchanged.
|
|
834
|
+
- `ObjectMetricWidgetProps.aggregate.groupBy` said `string`. That was a claim
|
|
835
|
+
about the author which nothing upstream backed: the value crosses two `any`
|
|
836
|
+
seams on the way in, so the declaration refused the node at neither compile
|
|
837
|
+
time nor runtime. It is now the contract's union, taken by reference through
|
|
838
|
+
`ObjectChartSchema['aggregate']`.
|
|
839
|
+
|
|
840
|
+
Unchanged, and pinned as controls: a plain string `groupBy` and an absent one
|
|
841
|
+
(floored at the single `'_all'` bucket) still take the legacy query byte for
|
|
842
|
+
byte, and an ARRAY `groupBy` still travels there too — it is not this union's
|
|
843
|
+
object arm, and it must keep reaching the producer-side refusal `503cd8b89`
|
|
844
|
+
landed in the adapter.
|
|
845
|
+
- 093af32: Take the `@objectstack/*` line to 17.4.0 and follow every contract it moved
|
|
846
|
+
(objectui#8772, with #7783 · #7663 · #8172 · #7845 · #8785).
|
|
847
|
+
|
|
848
|
+
**Breaking on one authored key, deliberately.** A dashboard node's
|
|
849
|
+
`refreshInterval` is now `refreshIntervalSeconds` — the value is unchanged
|
|
850
|
+
(seconds), and the spec refuses the old spelling by name with a message naming
|
|
851
|
+
the new one, so an existing document fails loudly at parse rather than silently
|
|
852
|
+
losing its auto-refresh. `os migrate meta --from 17` lists the mechanical edits.
|
|
853
|
+
The report component's own `refreshInterval` is a different key and is NOT
|
|
854
|
+
affected. (Scored `minor`, not `major`: this repo's fixed group tracks the
|
|
855
|
+
`@objectstack` major — AGENTS.md §版本号策略.)
|
|
856
|
+
|
|
857
|
+
Also authored-surface changes, all following a published spec move rather than a
|
|
858
|
+
local decision:
|
|
859
|
+
|
|
860
|
+
- `element:record_picker`'s `filter` input is now the `ViewFilterRule` array
|
|
861
|
+
form `[{ field, operator, value }, …]`. The MongoDB-style record form is
|
|
862
|
+
refused by the contract (objectstack#14406 converged the last record-form
|
|
863
|
+
`filter` in the map), so a JSX page writing the array form no longer draws a
|
|
864
|
+
false `type-mismatch` and one writing the record form is told.
|
|
865
|
+
- `object-kanban` now publishes `limit`, the row cap its renderer has always
|
|
866
|
+
lowered to `$top`. The spec declares it as of objectstack#16503, so the key
|
|
867
|
+
the docs teach is finally one the save gate stores.
|
|
868
|
+
- `object-gantt`'s ten extension keys (`timeSegments`, `interactions`,
|
|
869
|
+
`lockField`, …) are derived from the spec's `GanttConfigSchema` instead of
|
|
870
|
+
being re-declared locally. Same accept set on the flat face; the nested
|
|
871
|
+
`gantt` block narrows to the spec's, which now refuses an undeclared sub-key
|
|
872
|
+
by name where its `.passthrough()` window used to admit one.
|
|
873
|
+
- The console's preview-gallery samples — the worked examples an author copies —
|
|
874
|
+
move with two spec changes of their own: a job's `timeout` is now `timeoutMs`
|
|
875
|
+
(same unit-in-the-key-name ruling as the dashboard key above), and a flow's
|
|
876
|
+
end node writes `outcome: 'completed'`, the enum having narrowed to
|
|
877
|
+
`completed | refused`.
|
|
878
|
+
- 2596b1b: Route `object-metric`'s drill-down through the shared `DrillDownDrawer` (objectui#8970).
|
|
879
|
+
|
|
880
|
+
`ObjectMetricWidget` built its own drill panel inline — a Radix `Dialog` when
|
|
881
|
+
`drillDown.target` was the literal `dialog`, a `Sheet` otherwise, with a record-list
|
|
882
|
+
body it constructed itself. That copy read `enabled`, `target`'s two in-place arms,
|
|
883
|
+
`title` and `report`, and had no read site for the rest of the `DrillDownConfig` the
|
|
884
|
+
designer inspector offers. Three members were therefore accepted from the author and
|
|
885
|
+
discarded on this block alone, while acting on every other block that shares the same
|
|
886
|
+
config — no diagnostic, no rejection, and the identical tile on a `dataset` widget in a
|
|
887
|
+
metric presentation worked.
|
|
888
|
+
|
|
889
|
+
**Now delivered on `object-metric`:**
|
|
890
|
+
|
|
891
|
+
- **`target: 'navigate'`** — opens the object's full list page through the host's drill
|
|
892
|
+
navigation, scoped by the metric's own filter. Previously it fell into the panel
|
|
893
|
+
branch and drew the side sheet *unconditionally*. That mattered beyond the missing
|
|
894
|
+
feature: `DrillDownConfig.target` documents this arm as falling back to `'drawer'`
|
|
895
|
+
"when none is available", so the old behaviour read as the documented one while being
|
|
896
|
+
a different one. The fallback is now genuinely conditional.
|
|
897
|
+
- **`columns`** — column whitelist for the drilled record list.
|
|
898
|
+
- **`maxRows`** — page size of the drilled record list.
|
|
899
|
+
|
|
900
|
+
**Two deliberate behaviour changes that come with the shared component:**
|
|
901
|
+
|
|
902
|
+
- The drilled record list no longer shows a search box. The inline panel never chose
|
|
903
|
+
this; it inherited the record table's default while the shared drawer opts out, so a
|
|
904
|
+
metric drill is now the same self-contained peek as a pivot, chart or dataset drill.
|
|
905
|
+
- The drill sheet's widest breakpoint narrows by one step, from `lg:max-w-5xl` to the
|
|
906
|
+
shared drawer's `lg:max-w-4xl`. The centred `dialog` arm is unchanged.
|
|
907
|
+
|
|
908
|
+
The list's default page size is **unchanged at 25** — the value the inline panel
|
|
909
|
+
hard-coded is kept as this block's fallback, so a config that never authored `maxRows`
|
|
910
|
+
drills exactly as before rather than dropping to the record table's default of 10.
|
|
911
|
+
|
|
912
|
+
**Unchanged, and left to objectui#8970's own open question:** `drillDown.filter` and
|
|
913
|
+
`drillDown.mode` still have no read site on this block. The drilled list stays scoped by
|
|
914
|
+
the metric's resolved filter, which is what the block's registration promises ("the same
|
|
915
|
+
filter narrows the drill-down list, so the number and the records behind it always
|
|
916
|
+
agree"); a metric also has no click event for `${event.*}` to interpolate against. The
|
|
917
|
+
shared drawer honours neither member either, so routing through it does not settle them.
|
|
918
|
+
- 8120866: Converge the metric tile's percent `format` branch onto the repo's single percent
|
|
919
|
+
display rule (objectui#9165).
|
|
920
|
+
|
|
921
|
+
`formatMetricValue` in `MetricWidget.tsx` held a **second, drifted copy** of the
|
|
922
|
+
percent display rule. It was spelled inside out — a pass-through guarded by a
|
|
923
|
+
greater-than-one test with the multiply in the else arm — so it scaled everything
|
|
924
|
+
at or below `1` and passed through everything above, where the declared source
|
|
925
|
+
(`percentDisplayValue` in `@object-ui/core`) is the symmetric
|
|
926
|
+
`value > -1 && value < 1`. The negative half was unguarded and the boundary sat
|
|
927
|
+
on the wrong side of `1`. Because of that spelling, objectui#9071's census could
|
|
928
|
+
not see it: "the only `num <= 1` spelling in the repo" was true of the SPELLING
|
|
929
|
+
and false of the DRIFT.
|
|
930
|
+
|
|
931
|
+
The branch now hands the raw stored value to `formatPercent` — the identical call
|
|
932
|
+
this package's own `renderFieldValue` already makes (objectui#5607) and the one
|
|
933
|
+
the list-view percent cell makes — so it takes BOTH halves the shared helper's doc
|
|
934
|
+
comment demands of a third surface: the SCALING and the locale's percent
|
|
935
|
+
CONVENTION. The decimal count still comes from this surface's numeral pattern
|
|
936
|
+
(`'0.00%'` renders two decimals), which is an author declaration on the widget
|
|
937
|
+
rather than a guess about the value.
|
|
938
|
+
|
|
939
|
+
**BREAKING — stored data renders differently on a KPI tile after this change.**
|
|
940
|
+
Nothing about the stored value moves; what moves is how the tile prints it. On a
|
|
941
|
+
`metric-card` with a percent pattern such as `'0%'`, measured against the
|
|
942
|
+
list-cell path in the same run:
|
|
943
|
+
|
|
944
|
+
| stored | before | after | why |
|
|
945
|
+
|---|---|---|---|
|
|
946
|
+
| `1` | `100%` | `1%` | exactly `1` is percentage points, not a fraction |
|
|
947
|
+
| `-1` | `-100%` | `-1%` | the negative half was unguarded |
|
|
948
|
+
| `-5` | `-500%` | `-5%` | same |
|
|
949
|
+
| `1234.5` | `1235%` | `1,235%` | the locale's grouping (objectui#4553's move) |
|
|
950
|
+
| `1234.5` (`de-DE`) | `1235%` | `1.235 %` | the locale's percent affix, no-break space included |
|
|
951
|
+
| `0.25` | `25%` | `25%` | unchanged — a fraction, scaled as before |
|
|
952
|
+
| `12.3` | `12%` | `12%` | unchanged — already percentage points |
|
|
953
|
+
|
|
954
|
+
The first three rows are the defect: the same stored number read `100%` on a KPI
|
|
955
|
+
tile and `1%` in the list cell beside it, which users report as a data bug rather
|
|
956
|
+
than a formatting one. The next two are the convention half, which brings the tile
|
|
957
|
+
in line with the list cell, the summary chip and the dataset measure formatter.
|
|
958
|
+
|
|
959
|
+
**Level.** `minor`, not `major` and not `patch`. This repo keeps its major aligned
|
|
960
|
+
with `@objectstack`'s (see AGENTS.md, "版本号策略") and ships breaking changes as
|
|
961
|
+
`minor` with a written breaking note; `scripts/check-changeset-no-major.mjs`
|
|
962
|
+
enforces that mechanically, and all packages sit in one `fixed` group so a split
|
|
963
|
+
changeset could not produce split levels anyway. `patch` would be wrong because
|
|
964
|
+
values that render correctly today (four-digit percents, every non-`en` session)
|
|
965
|
+
render differently afterwards.
|
|
966
|
+
|
|
967
|
+
**Migration.** If a dashboard stored whole-percent values in the `0`–`1` band and
|
|
968
|
+
relied on the tile's old scaling to print them (a stored `1` shown as `100%`), it
|
|
969
|
+
was already disagreeing with every other percent surface in the console; store the
|
|
970
|
+
fraction (`0.01`) or the points (`1`) consistently and both surfaces now agree.
|
|
971
|
+
|
|
972
|
+
⚠️ **Dated note, 2026-10-02 — the magnitude guess is retired in this same release — objectui#11475.**
|
|
973
|
+
Later in this same release `percentDisplayValue` (`@object-ui/core`) stopped
|
|
974
|
+
inferring a percentage's storage from the value: it takes the storage as a
|
|
975
|
+
required argument, and every percent face passes the storage the field
|
|
976
|
+
declares (`percentScaleOf` in `@objectstack/spec/data`: a fraction unless the
|
|
977
|
+
field declares a `max` above 1). So a `metric` tile's `%` pattern states a fraction, numeral's own reading, and
|
|
978
|
+
a stored `1` reads `100%` on that tile again; an `object-metric` tile over a
|
|
979
|
+
`percent` field renders at that field's storage. The `1` → `1%` row above no
|
|
980
|
+
longer describes the tile. The rest of this entry is kept as the reading of this change.
|
|
981
|
+
- e356c39: Converge the bare `dashboard` key on `plugin-dashboard`, and retire
|
|
982
|
+
`view:dashboard` with a by-name tombstone (objectui#9533).
|
|
983
|
+
|
|
984
|
+
**Breaking for authored metadata.** `{ "type": "view:dashboard" }` no longer
|
|
985
|
+
renders a dashboard. It now resolves to a refusal that names the spelling and
|
|
986
|
+
names its replacement. `objectui validate` refuses the document at `type`
|
|
987
|
+
(`invalid_union`). `{ "type": "dashboard" }` and
|
|
988
|
+
`{ "type": "plugin-dashboard:dashboard" }` both render `DashboardRenderer`,
|
|
989
|
+
unchanged and newly-working respectively.
|
|
990
|
+
|
|
991
|
+
⚠️ **Dated note, 2026-09-25 — `objectui check` names the spelling only in a
|
|
992
|
+
file it recognises — objectui#10606.** This entry first said `objectui check`
|
|
993
|
+
reports `{ "type": "view:dashboard" }` as an unknown schema type, and **What
|
|
994
|
+
changed** below said, unscoped, that `check` names the spelling. `check` checks
|
|
995
|
+
the `type` of each file it recognises, and a file whose root carries an ObjectUI
|
|
996
|
+
structural key (`children`, `className`, `body`, …) is recognised by that key
|
|
997
|
+
alone. A file with none of those keys is parsed against the schema and
|
|
998
|
+
recognised only if it validates; one that does not validate is listed by name
|
|
999
|
+
when its root `type` is on the known-type list `check` reads, and is otherwise
|
|
1000
|
+
counted as skipped. `view:dashboard` is withheld from that list, so the document
|
|
1001
|
+
above is counted as skipped ("no ObjectUI recogniser admitted") and gets no
|
|
1002
|
+
unknown-type line, while `{ "type": "view:dashboard", "className": "h-64" }` gets one.
|
|
1003
|
+
`check` exits non-zero on unreadable JSON only; the verdict is
|
|
1004
|
+
`objectui validate`'s.
|
|
1005
|
+
|
|
1006
|
+
**What was wrong.** `apps/console` declares the lazy stub for bare `dashboard`
|
|
1007
|
+
under `plugin-dashboard` — twice, in `preview-gallery.tsx` and
|
|
1008
|
+
`register-plugins.ts` — while this package registered the renderer as
|
|
1009
|
+
`view:dashboard`. Neither site passed `skipFallback`, so both also claimed the
|
|
1010
|
+
bare key (`Registry.register` and `Registry.registerLazy` share the
|
|
1011
|
+
`meta?.namespace && !meta?.skipFallback` branch, and `registerLazy` has no
|
|
1012
|
+
collision check at all, so nothing warned). Two consequences followed, and both
|
|
1013
|
+
were reproduced against a real `Registry` before anything here was written:
|
|
1014
|
+
|
|
1015
|
+
1. **The bare key was double-claimed, phase-dependently.** After the stub step
|
|
1016
|
+
bare `dashboard` declared namespace `plugin-dashboard`; after the chunk
|
|
1017
|
+
loaded the same key declared `view`. Which answer a host got depended on when
|
|
1018
|
+
it asked — the objectui#6416 shape, which converged `plugin-report` off it.
|
|
1019
|
+
2. **The namespaced stub was never cleared.** `register()` clears the lazy stub
|
|
1020
|
+
of the type IT registers, and that type was `view:dashboard`. So
|
|
1021
|
+
`hasLazy('dashboard', 'plugin-dashboard')` read `true` after the package had
|
|
1022
|
+
fully loaded and `get('dashboard', 'plugin-dashboard')` read `undefined`,
|
|
1023
|
+
while the generated CLI whitelist listed that spelling as renderable. A node
|
|
1024
|
+
authored with it could only ever paint `Loading plugin-dashboard:dashboard…`
|
|
1025
|
+
forever: `Registry.loadLazy` resolves whether or not the loaded module
|
|
1026
|
+
registered the expected type, and `SchemaRenderer` re-checks `hasLazy` on
|
|
1027
|
+
every pass. That is the objectui#8760 shape — a key that passes every
|
|
1028
|
+
authoring check and fails only in front of a user.
|
|
1029
|
+
|
|
1030
|
+
**What changed.** The renderer registers as `plugin-dashboard:dashboard`, the
|
|
1031
|
+
namespace every sibling plugin, both console stubs and the CLI whitelist already
|
|
1032
|
+
use, so the console stubs are cleared on load and all three claimants of the bare
|
|
1033
|
+
key name ONE full type. The retired `view:dashboard` key answers
|
|
1034
|
+
`RetiredDashboardNodeTombstone` — an inline refusal, plus a `console.error`
|
|
1035
|
+
carrying the same text — registered with `skipFallback: true` so it claims no
|
|
1036
|
+
bare key. Its spelling is withheld from the derived key universe by declaration
|
|
1037
|
+
in `scripts/check-doc-component-types.mjs`, the same disposition the
|
|
1038
|
+
`RETIRED_FIELD_TYPES` tombstones take, so `objectui check` names it in a file it
|
|
1039
|
+
recognises rather than blessing it; `packages/cli/src/utils/known-schema-types.ts`
|
|
1040
|
+
regenerates and loses that one entry.
|
|
1041
|
+
|
|
1042
|
+
**Also published: four new symbols on the package entry.** `src/index.tsx`
|
|
1043
|
+
re-exports `RETIRED_DASHBOARD_NODE_TYPES`, `RetiredDashboardNodeTombstone`,
|
|
1044
|
+
`reportRetiredDashboardNodeType` and `resetRetiredDashboardNodeTypeReports` from
|
|
1045
|
+
`./retired-node-types`, and `exports["."]` is what publishes them — so these are
|
|
1046
|
+
**published** surface, not an internal one, and removing or renaming one later is
|
|
1047
|
+
a breaking change like any other export. They exist for the same reason
|
|
1048
|
+
`@object-ui/fields` publishes its tombstone: the pin has to import the refusal
|
|
1049
|
+
text it asserts rather than restate it.
|
|
1050
|
+
|
|
1051
|
+
**Also visible in the DOM: the `data-obj-type` value on a dashboard grid.** That
|
|
1052
|
+
attribute carries the type the node was AUTHORED with, so the spelling that puts
|
|
1053
|
+
a dashboard grid on the page moves with the registration.
|
|
1054
|
+
`[data-obj-type="view:dashboard"]` no longer selects one — that node now renders
|
|
1055
|
+
the tombstone, which publishes `data-retired-node-type` and no `data-obj-type` at
|
|
1056
|
+
all — while `[data-obj-type="plugin-dashboard:dashboard"]` now does.
|
|
1057
|
+
`{ "type": "dashboard" }` is unchanged and still reads `dashboard`. A stylesheet,
|
|
1058
|
+
a selector or a DOM assertion keyed on the retired value stops matching, and
|
|
1059
|
+
stops matching silently, so it is declared here rather than left to be found.
|
|
1060
|
+
|
|
1061
|
+
**Ruled, not chosen.** Director seat summon #24, batch #152 item 5 letter 1,
|
|
1062
|
+
maintainer 「其他同意」 2026-09-18. The two rejected routes are on the record:
|
|
1063
|
+
converging on `view` would leave the whitelist advertising a dead
|
|
1064
|
+
`plugin-dashboard:dashboard`, and standing one side down resolves the naming
|
|
1065
|
+
contest but not the unsatisfiable stub.
|
|
1066
|
+
|
|
1067
|
+
**`Clause-②: yes`**, declared by that ruling: the published full name moves. Note
|
|
1068
|
+
the shape, because it is unusual — the accept set **narrows** (an authored
|
|
1069
|
+
`view:dashboard` stops resolving) and the clause is `yes` anyway, on the
|
|
1070
|
+
published-name move rather than on a widened surface. `minor` per AGENTS.md's
|
|
1071
|
+
版本号策略: objectui's own breaking changes take `minor`, with the breaking
|
|
1072
|
+
semantics written out here.
|
|
1073
|
+
|
|
1074
|
+
`packages/plugin-dashboard/src/__tests__/dashboardBareKeyOwnership.test.tsx` is
|
|
1075
|
+
the half that outlives the fix. It reads the package's real declared metadata
|
|
1076
|
+
back out of the registry and replays it into a fresh `Registry` in **both**
|
|
1077
|
+
orders — eager-then-lazy and lazy-then-eager — checking the bare key's declared
|
|
1078
|
+
namespace after every step, so order- and phase-independence are properties under
|
|
1079
|
+
test rather than properties of the file it imports. It also asserts `hasLazy` is
|
|
1080
|
+
false after the load, and asserts the `view:dashboard` refusal **together with**
|
|
1081
|
+
the tombstone's own migration text, because a pin that asserts only "no dashboard
|
|
1082
|
+
rendered" passes identically against a registration that was simply deleted —
|
|
1083
|
+
the one outcome the ruling refuses. `objectui#9264`'s shrink-only bare-name
|
|
1084
|
+
collision ledger loses its row: it is now empty.
|
|
1085
|
+
- 2e471a6: `dashboard` now publishes the authoring inputs its renderer already honours — `widgets`, `label`, `description`, `header`, `globalFilters`, `dateRange`, `refreshIntervalSeconds` — so `validateTree`, the generated `sdui.manifest.json` and `sdui-intrinsics.d.ts` stop warning authors off keys that work (previously only `columns`/`gap`/`className` were published, and every other honoured key drew `unknown-prop`). Each declared key is accepted by the spec's strict `DashboardSchema`, so the manifest never offers a key the save gate refuses. The legacy `title` spelling and the retired `aria` key stay deliberately unpublished and are pinned as such; the `schema.title || schema.label` fallback read is unchanged, so documents in the wild keep rendering their header title.
|
|
1086
|
+
- 91af675: dashboard: honour a widget's declared `chartConfig` on the inline chart relays, not only on the dataset path
|
|
1087
|
+
|
|
1088
|
+
`DashboardWidget.chartConfig` is declared on **every** dashboard widget, but only the ADR-0021 dataset path (`DatasetWidget`) read it. The two inline relays — `DashboardRenderer` and `DashboardGridLayout`, which compose the chart node for a widget bound to inline rows or to a `provider: 'object'` aggregate — mentioned `chartConfig` zero times, so an author who wrote `chartConfig.title` / `.subtitle` / `.description` / `.colors` / `.height` / `.showLegend` / `.showDataLabels` / `.annotations` / `.interaction` on such a widget parsed clean and got nothing on screen.
|
|
1089
|
+
|
|
1090
|
+
Both relays now lower those keys through the same `chartConfigPresentation` whitelist `DatasetWidget` uses (`@object-ui/core`), so one authored chart config means the same thing on every dashboard surface.
|
|
1091
|
+
|
|
1092
|
+
**Behaviour change, stated explicitly** — this is why the bump is `minor` and not a patch: a dashboard whose stored metadata ALREADY carries `chartConfig` on an inline-bound chart widget renders differently after this change. It draws the authored titles, accessible description, palette, plot height, data labels, annotations and interaction toggles that were previously dropped. Widgets that declare no `chartConfig` compose exactly what they composed before.
|
|
1093
|
+
|
|
1094
|
+
Five of the fourteen keys of the spec's `ChartConfigSchema` are not forwarded, and `@objectstack/spec` 17.5.0 refuses all five on a dashboard widget's chart config at parse. `type` is refused because the widget's own `type` already picks the chart family on this path. `xAxis` / `yAxis` / `series` are refused because the widget's derived bindings own the chart's structure: the spec's `DashboardWidgetChartConfigSchema` refuses all three (ADR-0021 · ADR-0049 D2), which answered the precedence question objectstack-ai/objectstack#17385 raised. `aria` is refused because nothing on this path reads it in EITHER spelling — measured by forwarding it anyway, as the nested object and again flattened onto the node's own `ariaLabel` / `ariaDescribedBy` / `role`: neither changed a single attribute on screen, because `ChartRenderer` drops every prop but `schema` and `onChartClick`. Rather than give it a reader, the spec retired the key (objectstack-ai/objectstack#17751).
|
|
1095
|
+
- 1d9100e: Dashboard `table` widget — a numeric **measure** column now sorts **descending
|
|
1096
|
+
on the first click** (objectui#5845).
|
|
1097
|
+
|
|
1098
|
+
The sortable headers shipped in objectui#5827 started every column ascending,
|
|
1099
|
+
which left the card's own motivating complaint — the largest industry rendering
|
|
1100
|
+
last — two clicks away. For a measure in an analytics widget the question a
|
|
1101
|
+
click asks is "who is biggest", so that column now cycles
|
|
1102
|
+
**descending → ascending → the dataset's own order**.
|
|
1103
|
+
|
|
1104
|
+
Unchanged: a **dimension** column, and a measure whose values are not numbers
|
|
1105
|
+
(a `min()`/`max()` over a text field), keep **ascending → descending → the
|
|
1106
|
+
dataset's own order** — the idiom the console's own DataTable uses. The
|
|
1107
|
+
first-click direction is decided by the same measure-and-all-values-are-numbers
|
|
1108
|
+
classification that decides right-alignment, so it follows the column's *role*
|
|
1109
|
+
rather than "it looks like digits": a digit-keyed dimension (a year, a quarter)
|
|
1110
|
+
still starts ascending.
|
|
1111
|
+
|
|
1112
|
+
Blanks still sort **last in both directions** — that arm is now what a measure
|
|
1113
|
+
column's very first click runs — and `aria-sort` reports the direction actually
|
|
1114
|
+
applied.
|
|
1115
|
+
- 62c0fa1: Dashboard `table` widget (dataset-backed, flat grouped table) — four rendering
|
|
1116
|
+
fixes so it reads as a table rather than a raw dump (objectui#5827):
|
|
1117
|
+
|
|
1118
|
+
- **Numeric columns right-align**, header and cells. `tabular-nums` was already
|
|
1119
|
+
applied to every cell and could not do its job while the digits were flushed
|
|
1120
|
+
left. A column qualifies when it is a MEASURE and every non-null value in it
|
|
1121
|
+
is a number — the declared column `type` cannot decide it, because the
|
|
1122
|
+
analytics executor stamps `type: 'number'` on every measure regardless of the
|
|
1123
|
+
aggregate, so a `min()`/`max()` over a text field would have been
|
|
1124
|
+
right-aligned on the strength of a label. Dimensions stay left-aligned even
|
|
1125
|
+
when their bucket keys are digits: a grouping axis is not a quantity.
|
|
1126
|
+
- **Sortable headers.** Each header is a keyboard-reachable button that cycles
|
|
1127
|
+
ascending → descending → back to the dataset's own order, with the console's
|
|
1128
|
+
existing sort indicator and `aria-sort` on the `th`. Blanks sort last in
|
|
1129
|
+
BOTH directions. The sort is client-side over the rows already fetched — the
|
|
1130
|
+
widget issues one `queryDataset` call and paginates nothing, so no round trip
|
|
1131
|
+
is involved.
|
|
1132
|
+
- **The empty-dimension bucket sorts last** in the default order instead of
|
|
1133
|
+
floating to the top. It keeps its `—` label; only its position moves.
|
|
1134
|
+
- **Row hover feedback on every row**, not only on a drillable one. A drillable
|
|
1135
|
+
row keeps its stronger accent fill.
|
|
1136
|
+
|
|
1137
|
+
The CSV export now follows the order the table is showing, which is the same
|
|
1138
|
+
"the CSV is the table's data" convention its cell text already followed.
|
|
1139
|
+
Drill-through is unaffected: rows are reordered as `(row, incoming index)`
|
|
1140
|
+
pairs, so a drill still resolves through the server's parallel raw-value
|
|
1141
|
+
sidecar.
|
|
1142
|
+
|
|
1143
|
+
Unchanged: the cross-tab (a `pivot` with ≥2 dimensions) renderer, the KPI and
|
|
1144
|
+
chart paths, and every non-dataset table in the console.
|
|
1145
|
+
- e719ebd: `data-table` reads the declared `header`; the producers translate `label` into it.
|
|
1146
|
+
|
|
1147
|
+
`TableColumn` declares `header: string` and does not declare `label`. The
|
|
1148
|
+
renderer's column normalization nonetheless read `header: col.header || col.label`,
|
|
1149
|
+
so the same key had one spelling the type admits and one only the runtime did.
|
|
1150
|
+
That alias is gone (objectui#5351), and the translation it used to perform happens
|
|
1151
|
+
once at each producer instead: metadata vocabulary in, adapter vocabulary out.
|
|
1152
|
+
|
|
1153
|
+
**This narrows what `data-table` accepts, so read this if you author `data-table`
|
|
1154
|
+
nodes by hand.** A column spelled `{ label: 'Stage', accessorKey: 'stage' }` on a
|
|
1155
|
+
directly authored `data-table` now renders a **headerless** column over live
|
|
1156
|
+
cells. Spell it `header` — the key `TableColumn` has always declared. Columns
|
|
1157
|
+
reaching `data-table` through `object-data-table`, `object-grid` or a related
|
|
1158
|
+
list are unaffected: those producers resolve `header` for you from the spec's
|
|
1159
|
+
`ListColumnSchema.label`, so every spelling they accepted before they still
|
|
1160
|
+
accept.
|
|
1161
|
+
|
|
1162
|
+
`@object-ui/core` gains `columnHeader()` alongside `columnIdentity()` — the reader
|
|
1163
|
+
producers use to cross that boundary. It is adapter-first (`header` wins over
|
|
1164
|
+
`label`), so an author who addressed the table directly is never overwritten.
|
|
1165
|
+
|
|
1166
|
+
`object-data-table` also gains a fix from the same move: a column carrying a
|
|
1167
|
+
`label` used to render a **blank** header there even while the alias existed,
|
|
1168
|
+
because the widget's field-meta enrichment overwrote the authored `label` before
|
|
1169
|
+
the adapter ever saw it. `{ field: 'stage', label: 'Stage' }` now renders "Stage".
|
|
1170
|
+
|
|
1171
|
+
The sibling `accessorKey: col.accessorKey || col.name` alias is **unchanged** here
|
|
1172
|
+
and still resolves. Retiring it is objectui#5120's remaining step, which is
|
|
1173
|
+
gated on two published skill guides that teach that spelling.
|
|
1174
|
+
- 9e725e0: Dashboard flat `table` widget: render the server's grand total as a `tfoot` row (objectui#5846).
|
|
1175
|
+
|
|
1176
|
+
The flat branch now requests the `[]` marginal grouping — the same server-side
|
|
1177
|
+
totals machinery the cross-tab has used since framework#1753 — and renders the
|
|
1178
|
+
answer under the console's existing `dashboard.total` label. Totals are computed
|
|
1179
|
+
by the server with each measure's TRUE aggregate (a `sum` sums, an `avg`/`min`/
|
|
1180
|
+
`max` reports the whole-set aggregate); nothing is recombined client-side. The
|
|
1181
|
+
row lives in `tfoot`, so it is exempt from sorting, renders below the `—` null
|
|
1182
|
+
bucket, and never joins the drillable rows.
|
|
1183
|
+
|
|
1184
|
+
No footer is rendered when the executor answers no `[]` grouping, and none is
|
|
1185
|
+
requested for a table truncated by `options.limit`, whose visible rows a
|
|
1186
|
+
whole-set total could not be reconciled against.
|
|
1187
|
+
|
|
1188
|
+
### Patch Changes
|
|
1189
|
+
|
|
1190
|
+
- 061f5e8: fix(dashboard): a dashboard filter declaring `object` now resolves its translated labels; the axis-title half reaches no surface
|
|
1191
|
+
|
|
1192
|
+
`@objectstack/spec` declares two dashboard surfaces translatable — a filter's `object` and an
|
|
1193
|
+
axis `title` — and the Console resolved neither, so an author could set a key the contract
|
|
1194
|
+
documents and nothing happened. They turned out to be **two defects, not one** — a key never
|
|
1195
|
+
read, and a value that bypassed a resolver already in the tree — and they were fixed
|
|
1196
|
+
separately. Only the first reaches an author in this release: see the note at the end.
|
|
1197
|
+
|
|
1198
|
+
**1. `GlobalFilterSchema.object` was never read.** The spec's describe text for the key is
|
|
1199
|
+
"Object whose `fields.<object>.<field>` translation-bundle entry resolves this filter's field
|
|
1200
|
+
label and option labels". `resolveDashboardFilterDefs` names the keys it copies onto a
|
|
1201
|
+
`DashboardFilterDef` and this one was not among them, so the authored value could not reach a
|
|
1202
|
+
renderer even in principle: a dashboard filter declaring `object` and no `label`, viewed on a
|
|
1203
|
+
translated console, painted the RAW FIELD NAME and left its option labels in the authored
|
|
1204
|
+
English. The definition now carries `object` through, and `DashboardFilterBar` resolves the
|
|
1205
|
+
field label through `useSafeFieldLabel().fieldLabel` and the option labels through the same
|
|
1206
|
+
object's `translateOptions` — the convention resolver every list and form already calls, which
|
|
1207
|
+
is the "one resolver path" the key's own spec text asks for. No second resolver was written.
|
|
1208
|
+
Precedence follows that resolver's signature: the translator's bundle wins, the authored label
|
|
1209
|
+
is the fallback. A filter that names no `object` never reaches it and renders exactly as
|
|
1210
|
+
before.
|
|
1211
|
+
|
|
1212
|
+
**2. `ChartAxisSchema.title` bypassed a resolver two modules downstream.** The key is
|
|
1213
|
+
`I18nLabel`, so an inline locale map parses, builds and validates — and `axisPresentation`
|
|
1214
|
+
collapsed it with `labelText`, a first-string-in-KEY-ORDER pick. Measured both ways round on
|
|
1215
|
+
one map: English to a `zh-CN` viewer and Chinese to an `en` viewer, decided by nothing but
|
|
1216
|
+
which key the author typed first. `normalizeChartSchema` already resolves an axis title
|
|
1217
|
+
through `pickLocalized` against the viewer's language, so the map only had to survive the
|
|
1218
|
+
lowering. The fix forwarded it verbatim through `forwardedI18nLabel` — the neighbour in the
|
|
1219
|
+
same module that already carries a chart's own `title` / `subtitle` / `description` for
|
|
1220
|
+
exactly this reason — and that lowering was then removed in the same release (the note at the
|
|
1221
|
+
end).
|
|
1222
|
+
|
|
1223
|
+
This moves one entry of the objectui#4020 first-string-wins ledger. That ledger's
|
|
1224
|
+
justification is "a locale-unaware pick a caller can OVERRIDE", which holds for a series
|
|
1225
|
+
`label` — the report renderer outranks it with objectui#4020's three-level display name — and
|
|
1226
|
+
never held for an axis title, which is spread onto the chart schema and drawn. `seriesPresentation` keeps the pick and
|
|
1227
|
+
is still pinned.
|
|
1228
|
+
|
|
1229
|
+
What an author sees change: a filter that opted in with `object` now shows its translated
|
|
1230
|
+
field and option labels instead of the raw field name and English options. Nothing that omits
|
|
1231
|
+
`object` renders differently, and point 2 changes nothing an author sees (below).
|
|
1232
|
+
|
|
1233
|
+
⚠️ Point 2 reaches no surface in this release, which also carries objectui#11315 and
|
|
1234
|
+
objectui#11372. `@objectstack/spec` 17.5.0 refuses `chartConfig.xAxis` / `yAxis` / `series` on
|
|
1235
|
+
a dashboard widget, and the dashboard's dataset widget no longer reads them (objectui#11315), so
|
|
1236
|
+
a dashboard draws no authored axis title at all, in any language. That widget was the only
|
|
1237
|
+
caller of `axisPresentation` and `mergeAuthoredPresentation`, and objectui#11372 removes both
|
|
1238
|
+
from `@object-ui/core` in the same release, so the fixed lowering never ships. The react
|
|
1239
|
+
`ObjectChart` tier, which still authors its own axes, never went through that lowering: it hands
|
|
1240
|
+
`xAxis` / `yAxis` to `normalizeChartSchema`, which resolves a locale-map axis title against the
|
|
1241
|
+
viewer's language since objectui#8943 (released alongside this, under its own changeset).
|
|
1242
|
+
`ObjectChart.axisTitleLocale-10132.test.tsx` in `@object-ui/plugin-charts` pins that surface at
|
|
1243
|
+
two languages.
|
|
1244
|
+
- e2bd3e4: fix(plugin-dashboard): a field's `format` is read as a date pattern only on a `date` / `datetime` field
|
|
1245
|
+
|
|
1246
|
+
The dashboard table (`object-data-table`) and the record-detail drawer render a
|
|
1247
|
+
value through `renderFieldValue`, whose date branch fired on any `format` string
|
|
1248
|
+
containing one of the letters Y, M, D, H, m or s, whatever the field's type. A
|
|
1249
|
+
text field hinted `format: 'email'` — which the shared cell-renderer resolver
|
|
1250
|
+
promotes to a `mailto:` link — contains an `m`, so its address went to
|
|
1251
|
+
`formatDate` and was painted as an em dash: the value disappeared. A text field
|
|
1252
|
+
hinted `format: 'money'` holding a number rendered as a 1970 date, an
|
|
1253
|
+
`autonumber` pattern with date tokens such as `ORD-{YYYYMMDD}-{00}` and a `time`
|
|
1254
|
+
field's `HH:mm` rendered as an em dash, all with no error.
|
|
1255
|
+
|
|
1256
|
+
The branch now runs only when the field's type is `date` or `datetime`. Those
|
|
1257
|
+
fields render exactly as before. A field of any other type no longer takes the
|
|
1258
|
+
date branch and falls through to the cell renderer its type and `format` hint
|
|
1259
|
+
select; the currency and percent branches ahead of it are unchanged. A column whose field
|
|
1260
|
+
type is unknown — no object-schema field and no `type` on the column — is no
|
|
1261
|
+
longer date-formatted from its `format` alone: declare `type: 'date'` on the
|
|
1262
|
+
column to keep the date face.
|
|
1263
|
+
- d461b0d: A fixed-currency field shows its own currency in the dashboard table widget and
|
|
1264
|
+
in the record drawer it opens, not the tenant's (objectui#10463).
|
|
1265
|
+
|
|
1266
|
+
`@objectstack/spec` refuses a field-level `currency`, so a fixed currency is
|
|
1267
|
+
declared as `currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'JPY' }`.
|
|
1268
|
+
`buildFieldMeta`, the helper both `ObjectDataTable` and `RecordDetailDrawer`
|
|
1269
|
+
build their field metadata with, read only the flat `currency` and
|
|
1270
|
+
`defaultCurrency` keys and never `currencyConfig`. In a USD tenant, a JPY-fixed
|
|
1271
|
+
amount of 1234 read `$1,234` in the table cell and in the drawer, while the
|
|
1272
|
+
grid and the metric tile read `¥1,234` for the same field.
|
|
1273
|
+
|
|
1274
|
+
`buildFieldMeta` now resolves the field's currency through
|
|
1275
|
+
`resolveFieldCurrency` from `@object-ui/i18n`, the resolver the grid, the
|
|
1276
|
+
metric tile and the currency cell already share. A column's own `currency`
|
|
1277
|
+
still wins over the field's. The resolver is called without the tenant
|
|
1278
|
+
default, so a column `format` that starts with a currency symbol still wins
|
|
1279
|
+
over the tenant currency, as before.
|
|
1280
|
+
|
|
1281
|
+
**Behaviour change.** In the dashboard table widget and the record drawer, a
|
|
1282
|
+
field whose `currencyConfig` is in `fixed` mode now shows
|
|
1283
|
+
`currencyConfig.defaultCurrency` where it used to show the tenant currency. A
|
|
1284
|
+
field in `dynamic` mode, or with a `currencyConfig` that names no
|
|
1285
|
+
`currencyMode`, still shows the tenant currency, because the resolver reads
|
|
1286
|
+
`currencyConfig.defaultCurrency` only in `fixed` mode (objectui#10422). A field
|
|
1287
|
+
that carries both a fixed `currencyConfig` and the legacy top-level
|
|
1288
|
+
`defaultCurrency` now shows the fixed code, which is the resolver's order.
|
|
1289
|
+
- 668dd59: Fix: the editable dashboard grid (`dashboard-grid`, `DashboardGridLayout`) now
|
|
1290
|
+
treats widgets whose data is `{ provider: 'object', object }` the way the
|
|
1291
|
+
read-only `DashboardRenderer` already does (objectui#10528). Before this change
|
|
1292
|
+
the grid turned a table, list or pivot widget with that config into a static
|
|
1293
|
+
node with `data: []`. The tile showed an empty table or cross-tab and sent no
|
|
1294
|
+
query.
|
|
1295
|
+
|
|
1296
|
+
**Table and list widgets now fetch their rows.** The grid builds the same node
|
|
1297
|
+
`DashboardRenderer` builds for the widget: an `object-data-table`, which loads
|
|
1298
|
+
the object's rows through the data source. The props match too:
|
|
1299
|
+
|
|
1300
|
+
- the provider's `filter` (or the widget's `filter`) reaches the query;
|
|
1301
|
+
- the widget's `searchable` and `pagination` are honoured, and a `list` never
|
|
1302
|
+
gets them;
|
|
1303
|
+
- a row click opens the record in a drawer by default, as on the read-only
|
|
1304
|
+
dashboard. `options.drillDown` still overrides this. In edit mode, dragging
|
|
1305
|
+
still starts only from the drag handle.
|
|
1306
|
+
|
|
1307
|
+
**Pivot widgets with that config now show the retired-widget placeholder.**
|
|
1308
|
+
It is the same placeholder `DashboardRenderer` already shows for them ("This
|
|
1309
|
+
widget uses a retired data format. Edit it to bind a dataset."). Under ADR-0021
|
|
1310
|
+
a pivot is bound to a dataset, and a dataset-bound pivot still renders as
|
|
1311
|
+
before.
|
|
1312
|
+
|
|
1313
|
+
Widgets bound to inline rows are unchanged, including static-data pivots.
|
|
1314
|
+
|
|
1315
|
+
⚠️ **Dated note, 2026-10-02 — the `dashboard-grid` node key is retired — objectui#10859.**
|
|
1316
|
+
Later in this same release objectui#10859 batch 8 (phase 2b) unregistered the `dashboard-grid`
|
|
1317
|
+
node key. `DashboardGridLayout` is unchanged and stays exported, so this fix still holds wherever
|
|
1318
|
+
a host mounts it; it is no longer reachable as a schema `type`. The rest of this entry is kept as
|
|
1319
|
+
the reading of this change.
|
|
1320
|
+
|
|
1321
|
+
⚠️ **Dated note, 2026-10-03 — the grid's static pivot node states its keys — objectui#11466.** At this change, "Widgets bound to inline rows are unchanged, including static-data pivots" held, and the grid's static-data pivot node spread the widget's `options` whole. Now that node states the keys `PivotTableSchema` declares and `PivotTable` draws, read from `options`, and no other option key reaches it. `.changeset/11466-dashboard-metric-node.md` states what ships. The rest of this entry is kept as the reading of this change.
|
|
1322
|
+
- 4a3d500: `object-metric` (`ObjectMetricWidget`) re-reads its value when the data-invalidation bus (`notifyDataChanged` from `@object-ui/react`) reports a change to the object it aggregates (objectui#10572). A write that bypasses the data source — a page action over raw HTTP, a flow, a server action — used to leave the tile stale until the whole page was remounted.
|
|
1323
|
+
- 9fbbb17: BREAKING (`@object-ui/components`): `RefreshIndicator`'s `ariaLabel` prop is now required and has no default. It used to default to the English literal "Refreshing", so the progress bar on every view that passed no name was announced in English to screen-reader users in every locale (objectui#10580). A `RefreshIndicator` rendered without `ariaLabel` now fails to type-check.
|
|
1324
|
+
|
|
1325
|
+
(The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own breaking changes ship as `minor` with the breaking semantics stated here.)
|
|
1326
|
+
|
|
1327
|
+
Migration: pass the bar's accessible name from your own translation layer, for example `ariaLabel={t('grid.refreshing')}`. The component renders the string you pass as the bar's `aria-label` and does not translate it.
|
|
1328
|
+
|
|
1329
|
+
`ObjectGrid`, `ListView`, `ObjectChart` and `ObjectDataTable` now name their refresh bar in the active locale. The grid and the list read the `grid.refreshing` and `list.refreshing` keys their pull-to-refresh text already uses. The chart reads `chart.refreshing` and the dashboard data table reads `dashboard.refreshing`, both with no English literal behind them, so a host that renders either one with no i18next instance at all gets the key itself as the bar's name.
|
|
1330
|
+
|
|
1331
|
+
`@object-ui/i18n`: new keys `chart.refreshing` and `dashboard.refreshing` in every built-in locale pack.
|
|
1332
|
+
- 9c78ebe: `object-data-table` sets `TableColumn.masked` on its columns, so a `password` / `secret` column no longer copies, exports, searches, sorts or sizes by its raw value (objectui#10657, which folded objectui#10658).
|
|
1333
|
+
|
|
1334
|
+
The dashboard table widget draws a `password` / `secret` cell as `••••••`, but it never set
|
|
1335
|
+
the flag the table reads. So the table handed the raw value out anyway: Ctrl+C / Cmd+C on
|
|
1336
|
+
the masked cell, its tooltip, the CSV export, the search box (which runs in the browser
|
|
1337
|
+
here) and the header sort all read it, and the column's width grew with it.
|
|
1338
|
+
|
|
1339
|
+
The widget now sets the flag on every column whose cell is drawn as a mask, from
|
|
1340
|
+
`isMaskedFieldType()` in `@object-ui/fields` over the column's authored `type` and the
|
|
1341
|
+
object's field type (a narrow-only union: a column authored as `text` over a `secret`
|
|
1342
|
+
field keeps the flag), for declared and derived columns alike.
|
|
1343
|
+
|
|
1344
|
+
It fails closed. Bound and inline rows are drawn before the object definition arrives, and
|
|
1345
|
+
a failed read never delivers one. In that window the widget cannot tell a masked column
|
|
1346
|
+
from any other, so it sets the flag on every column: no cell copies, shows a tooltip or is
|
|
1347
|
+
exported, the search matches nothing, no header sorts, and widths come from the headers.
|
|
1348
|
+
Not covered: in the same window a field the object declares as `password` / `secret` is
|
|
1349
|
+
drawn as text, because the mask comes from that declaration.
|
|
1350
|
+
|
|
1351
|
+
This is defence in depth. The real guard is still the server: ObjectStack's read mask
|
|
1352
|
+
replaces `secret` and `password` values with its mask before they reach the browser.
|
|
1353
|
+
- 12809a5: `object-data-table` no longer draws an untyped column as text while the object definition is loading or after its read failed: it draws the mask instead (objectui#10657, the objectui#10706 class at this producer).
|
|
1354
|
+
|
|
1355
|
+
A cell here draws from the column's authored `type`, or the object's. Bound and inline
|
|
1356
|
+
rows are drawn before the object definition arrives, and a failed read never delivers
|
|
1357
|
+
one, so in that window a column that authors no `type` drew its value as text, a
|
|
1358
|
+
`password` / `secret` field included. objectui#10657's first change already set
|
|
1359
|
+
`TableColumn.masked` on every column in that window, but the flag withholds; it does not
|
|
1360
|
+
draw.
|
|
1361
|
+
|
|
1362
|
+
Now, in that window, a column with no `type` of its own is drawn as the mask
|
|
1363
|
+
(`MaskedCellRenderer` from `@object-ui/fields`), declared and derived columns alike, and
|
|
1364
|
+
stays so when the read failed: fail closed, never back to text. A column that authors its
|
|
1365
|
+
`type` draws from it, and a column with a `cell` of its own draws what that `cell`
|
|
1366
|
+
returns. Once the definition lands, each column draws from its declared type as before.
|
|
1367
|
+
|
|
1368
|
+
The record drawer a row opens (record drill-down) drew the clicked record's fields from
|
|
1369
|
+
the same unknown types, so after a failed read it printed a `password` / `secret` field
|
|
1370
|
+
as text too. In that window it now draws every value as the mask.
|
|
1371
|
+
|
|
1372
|
+
The pending objectui#10657 `object-data-table` changeset says a `password` / `secret`
|
|
1373
|
+
field is still drawn as text in that window; that no longer holds once this change ships
|
|
1374
|
+
with it.
|
|
1375
|
+
|
|
1376
|
+
This is defence in depth. The real guard is still the server: ObjectStack's read mask
|
|
1377
|
+
replaces `secret` and `password` values with its mask before they reach the browser.
|
|
1378
|
+
- 7afc81d: The dashboard's `object-data-table` reads its rows once per mount, already expanded, and a dashboard filter re-reads its options when `optionsFrom.filter` changes (objectui#10664).
|
|
1379
|
+
|
|
1380
|
+
- `object-data-table` loaded the object definition in an effect of its own and listed it among the fetch effect's dependencies, so every object-bound mount issued two `find` calls, the first without `$expand`. It now reads the definition through `useSettledSchema` from `@object-ui/react` and its query waits until that read settles; a failed read, or an adapter with no `getObjectSchema`, still loads the rows, unexpanded. Its expansion also depends on `columns`, which did not re-run the read: adding a lookup column to a mounted table now re-reads with the new `$expand`, and relabelling a column does not.
|
|
1381
|
+
- The filter bar's option list sends `optionsFrom.filter` on both of its reads, the dataset query and the record fallback, but did not re-read when only that filter changed. It now does, and an equal filter in a new object does not re-read.
|
|
1382
|
+
- 5ad3b88: `object-data-table` refuses `drillDown.filter`, `.maxRows`, `.report` and `target: 'navigate'`, and `object-pivot` refuses `drillDown.mode` (objectui#10685)
|
|
1383
|
+
|
|
1384
|
+
**Breaking for authored metadata, graded `minor` by this repo's convention (AGENTS.md: a
|
|
1385
|
+
breaking change here is `minor`, never `major`):** `object-data-table` no longer accepts
|
|
1386
|
+
`drillDown.filter`, `drillDown.maxRows`, `drillDown.report` or `drillDown.target: 'navigate'`
|
|
1387
|
+
on either published face — the TypeScript `ObjectDataTableSchema.drillDown` and its zod
|
|
1388
|
+
mirror, the door `objectui validate` / `safeValidateSchema` run — so a stored JSON table
|
|
1389
|
+
config that parsed green before is refused by name now. Each refusal names the key and the
|
|
1390
|
+
blocks that do read it. None of the four was ever read by this block. Migration: delete the
|
|
1391
|
+
key; for the drill target write `'drawer'` or `'dialog'`. `enabled`, `mode`, `title` and
|
|
1392
|
+
`columns` are accepted exactly as before.
|
|
1393
|
+
|
|
1394
|
+
Its row drills to the one record it already is, so a drilled list's filter, row cap and
|
|
1395
|
+
report had nothing to act on, and `'navigate'` was drawn as a drawer. Before this change the
|
|
1396
|
+
table's zod mirror took the shared `DrillDownConfigSchema` and accepted all four.
|
|
1397
|
+
|
|
1398
|
+
`object-pivot` is refused at the TypeScript door only. A stored JSON pivot config carrying
|
|
1399
|
+
`mode` is still accepted and ignored at render, as it was before: neither `object-pivot` nor
|
|
1400
|
+
`PivotTableSchema` has a zod mirror, so no validator in this repository reads the members of
|
|
1401
|
+
a pivot's `drillDown`. `mode` never had a read site on a pivot. Every pivot click point is an
|
|
1402
|
+
aggregated bucket (a cell, a row or column header, or a total), so the block always drills
|
|
1403
|
+
through, and `mode`, which chooses drill-to-record for a clicked row, had nothing to choose.
|
|
1404
|
+
|
|
1405
|
+
- `@object-ui/types` adds two per-block shapes beside `ObjectMetricDrillDownConfig`, both
|
|
1406
|
+
published on the root entry `@object-ui/types` and on the `@object-ui/types/data-display`
|
|
1407
|
+
subpath. Each tombstone's docblock names the blocks that do read the key.
|
|
1408
|
+
- `ObjectDataTableDrillDownConfig`: `DrillDownConfig` with `filter?: never`,
|
|
1409
|
+
`maxRows?: never` and `report?: never`, and `target?: 'drawer' | 'dialog'`.
|
|
1410
|
+
`ObjectDataTableSchema.drillDown` is typed with it, and the `drillDown` member of its zod
|
|
1411
|
+
mirror (`@object-ui/types/zod`) refuses the same members by name.
|
|
1412
|
+
- `ObjectPivotDrillDownConfig`: `DrillDownConfig` with `mode?: never`.
|
|
1413
|
+
- The shared `DrillDownConfig` keeps every member for the blocks that read them. Its `mode`
|
|
1414
|
+
docblock now names only `object-data-table` instead of charts, pivot tables and metric
|
|
1415
|
+
cards, none of which read it.
|
|
1416
|
+
- `@object-ui/plugin-dashboard` types `ObjectPivotTable`'s `schema.drillDown` with
|
|
1417
|
+
`ObjectPivotDrillDownConfig`, and the component reads its drill config through that type
|
|
1418
|
+
instead of through `any`. `ObjectDataTable`'s prop is `ObjectDataTableSchema`, so it takes
|
|
1419
|
+
the table's shape with it.
|
|
1420
|
+
|
|
1421
|
+
TSX code now fails to compile in two places. The first is a refused member inside
|
|
1422
|
+
`ObjectDataTableSchema.drillDown`, or inside `ObjectPivotTable`'s `schema.drillDown`. The
|
|
1423
|
+
second is a value typed as the shared `DrillDownConfig` handed to either. Delete the key.
|
|
1424
|
+
Runtime rendering is unchanged.
|
|
1425
|
+
|
|
1426
|
+
⚠️ **Dated note, 2026-09-28 — the plain `pivot` node has a mirror now — objectui#10859.**
|
|
1427
|
+
Later in this same release `PivotTableSchema` (the `pivot` node, not `object-pivot`) gains a
|
|
1428
|
+
zod mirror whose `drillDown` is the shared `DrillDownConfigSchema`, so a validator does read
|
|
1429
|
+
the members of a `pivot` node's `drillDown`, and accepts `mode` there, as `DrillDownConfig`
|
|
1430
|
+
declares it. `object-pivot` still has no mirror, so what the paragraph above says of a stored
|
|
1431
|
+
`object-pivot` config stands. The rest of this entry is kept as the reading of this change.
|
|
1432
|
+
|
|
1433
|
+
⚠️ **Dated note, 2026-09-29 — the `pivot` node refuses `drillDown` whole now — objectui#10932.**
|
|
1434
|
+
Later in this same release `drillDown` was retired on the `pivot` node on both faces:
|
|
1435
|
+
`PivotTableSchema.drillDown` is a `?: never` tombstone, and the `pivot` arm of the zod mirror
|
|
1436
|
+
declares it as a `retirementTombstone()`, which refuses the key by name whatever it holds
|
|
1437
|
+
(`invalid_type` at `drillDown`). So "a validator does read the members of a `pivot` node's
|
|
1438
|
+
`drillDown`, and accepts `mode` there" in the 2026-09-28 note above no longer holds: the
|
|
1439
|
+
validator refuses the whole key before reading any member, so it accepts no `mode` on a `pivot`
|
|
1440
|
+
node. Nothing ever drilled a `pivot` node; a pivot drill is authored on `object-pivot`, whose
|
|
1441
|
+
`drillDown` type, `ObjectPivotDrillDownConfig`, refuses `mode` at the TypeScript door, as this
|
|
1442
|
+
entry says. `object-pivot` still has no zod mirror, so what this entry says of a stored
|
|
1443
|
+
`object-pivot` config stands. `.changeset/10932-pivot-drilldown-retired.md` (PR objectui#10972)
|
|
1444
|
+
states what ships; the text above is kept as the reading of this change.
|
|
1445
|
+
|
|
1446
|
+
⚠️ **Dated note, 2026-10-02 — `object-pivot` has a zod arm now — objectui#11440.**
|
|
1447
|
+
Later in this same release `object-pivot` gained an arm in `@object-ui/types/zod`,
|
|
1448
|
+
`ObjectPivotBlockSchema`, whose `properties.drillDown` is this entry's per-block shape: the shared
|
|
1449
|
+
drill config with `mode` refused by name (`invalid_type` at `properties.drillDown.mode`). So "A
|
|
1450
|
+
stored JSON pivot config carrying `mode` is still accepted" and "neither `object-pivot` nor
|
|
1451
|
+
`PivotTableSchema` has a zod mirror, so no validator in this repository reads the members of a
|
|
1452
|
+
pivot's `drillDown`" above, and "`object-pivot` still has no zod mirror" in the 2026-09-29 note, no
|
|
1453
|
+
longer hold: `safeValidateSchema` and the strict face refuse that `mode`.
|
|
1454
|
+
`.changeset/11440-arm-passing-types.md` states what ships. The rest of this entry is kept as the
|
|
1455
|
+
reading of this change.
|
|
1456
|
+
- 8c3d7fc: fix(plugin-dashboard): `object-pivot` and `object-data-table` re-read on the data-invalidation bus
|
|
1457
|
+
|
|
1458
|
+
The fetch effects of `ObjectPivotTable` and `ObjectDataTable` now name the
|
|
1459
|
+
`useDataInvalidation` nonce for `schema.objectName`, so a write declared on the
|
|
1460
|
+
bus (`notifyDataChanged`, as a page action over raw HTTP does) re-reads the rows
|
|
1461
|
+
in place. Before, both refreshed after such a write only when their host
|
|
1462
|
+
remounted them, and `PageView` is about to stop doing that (objectui#10519).
|
|
1463
|
+
|
|
1464
|
+
The pivot's element-bound spelling (`dataSource: { object }`) resolves onto the
|
|
1465
|
+
same key. The table is also what a `dashboard` block draws for a table widget
|
|
1466
|
+
over `{ provider: 'object' }` and what a drill-down drawer lists the underlying
|
|
1467
|
+
records with, so those re-read too. A drill that opens a report
|
|
1468
|
+
(`drillDown.report`) and the single-record drawer a table row opens do not
|
|
1469
|
+
re-read. Bound rows and authored `data` rows do not subscribe.
|
|
1470
|
+
|
|
1471
|
+
⚠️ **Dated note, 2026-09-27 — the report a drill opens does re-read — objectui#10853.**
|
|
1472
|
+
The sentence above that a drill opening a report (`drillDown.report`) does not
|
|
1473
|
+
re-read stopped holding with objectui#10814 (PR objectui#10836), pending in the
|
|
1474
|
+
same release: the drawer's `drillDown.report` arm renders `spec-report` through
|
|
1475
|
+
`SchemaRenderer`, and the dataset report renderer's own reader re-reads it on the
|
|
1476
|
+
bus. `drillDownReport.invalidationRefetch-10814.test.tsx` in `apps/console` pins
|
|
1477
|
+
that through the real drawer. The single-record drawer a table row opens still
|
|
1478
|
+
does not re-read. The rest of this entry is kept as the reading of this change.
|
|
1479
|
+
|
|
1480
|
+
⚠️ **Dated note, 2026-10-02 — the drawer renders a `report` node — objectui#11440.**
|
|
1481
|
+
Later in this same release the drawer's `drillDown.report` arm renders `{ type: 'report', report }`
|
|
1482
|
+
through `SchemaRenderer`, not `spec-report`, which `@object-ui/plugin-report` retired. So "renders
|
|
1483
|
+
`spec-report` through `SchemaRenderer`" in the 2026-09-27 note no longer holds; the report it opens
|
|
1484
|
+
still re-reads on the bus, as that note says. `.changeset/11440-drill-drawer-report-node.md` states
|
|
1485
|
+
what ships. The rest of this entry is kept as the reading of this change.
|
|
1486
|
+
- 256b4c9: A filter refused as malformed (`INVALID_FILTER`) no longer throws out of render or out of a click
|
|
1487
|
+
handler at the sites that merge filters there. That holds whether this layer's filter converter
|
|
1488
|
+
refused it or, at the drill seam, the spec's own `parseFilterAST`. Each site now surfaces the
|
|
1489
|
+
refusal the way its siblings already do (objectui#10789). The filter stays **refused**: nothing
|
|
1490
|
+
that was refused is accepted, and no site falls back to "no filter".
|
|
1491
|
+
|
|
1492
|
+
- **`ElementDataSourceGate` / `useElementDataSource`** (`@object-ui/react`): the gate's merge of
|
|
1493
|
+
the component's `filter` with the composed binding, and the merge of a saved view's filter
|
|
1494
|
+
with the binding's own, lowered through the throwing converter inside render. A refused
|
|
1495
|
+
filter from either now makes the binding `missing` with a new optional `filterRefusal` on the
|
|
1496
|
+
result (`error` carries its message). The gate then draws, in place of the block, the
|
|
1497
|
+
malformed-filter notice the wrapped blocks draw: `view.malformedFilter`, naming the refused
|
|
1498
|
+
operator or field. `element:record_picker`, which reads the same hook, shows its
|
|
1499
|
+
configuration-error panel with the refusal.
|
|
1500
|
+
- **`PeoplePicker` / `RecordPickerDialog`** (`@object-ui/fields`): the recents merge and the
|
|
1501
|
+
dialog's `mergedFilter` lower through `toFilterNodeSafely`; a refusal is shown in the picker's
|
|
1502
|
+
existing error state and no query runs without the filter.
|
|
1503
|
+
- **Drill-downs** (`ObjectChart`, `ObjectPivotTable`, `DatasetWidget`, `DatasetReportRenderer`):
|
|
1504
|
+
a widget or report filter refused at the drill seam made the drill click throw. The converter
|
|
1505
|
+
refuses some (a spec `$not`, say). The spec's `parseFilterAST` refuses others, such as a scalar
|
|
1506
|
+
on a list operator in either dialect: `[['stage', 'in', 'won']]` or `{ stage: { $in: 'won' } }`.
|
|
1507
|
+
The drill is now not opened or emitted (never with the scope dropped), and the refusal is logged
|
|
1508
|
+
with `console.warn`, naming the operator. `composeDrillFilter` (`@object-ui/core`) now throws
|
|
1509
|
+
one refusal type: the spec's `INVALID_FILTER` error is re-raised as a `FilterOperatorError` with
|
|
1510
|
+
its message and the same `INVALID_FILTER` / 400 envelope, and any other error passes through
|
|
1511
|
+
unchanged.
|
|
1512
|
+
- **`ListView` export** (`@object-ui/plugin-list`): the server export builds its filter inside
|
|
1513
|
+
its `try`, so a refusal is named in the export menu instead of escaping the click.
|
|
1514
|
+
- **`convertFiltersToAST`** (`@object-ui/core`): an `$or` member that is the TRUE identity no
|
|
1515
|
+
longer stops the members after it from being read. `{ $or: [{}, { a: {} }] }` is now refused
|
|
1516
|
+
like `{ $or: [{ a: {} }, {}] }` already was, instead of answering TRUE (every row); a TRUE
|
|
1517
|
+
disjunct beside members that lower still absorbs its `$or`.
|
|
1518
|
+
- c5ec15c: fix: every `Calendar` picker opens on its selected date's month, not today's (objectui#10799)
|
|
1519
|
+
|
|
1520
|
+
react-day-picker opens on `month`, else `defaultMonth`, else today, and
|
|
1521
|
+
`selected` does not move it. Five `Calendar` mounts passed `selected` only, so
|
|
1522
|
+
a value outside the current month opened the picker on today's month: with
|
|
1523
|
+
the calendar on 4 March 2020 and today in September 2026, the header of
|
|
1524
|
+
`CalendarView` read `March 2020` while the popover under it opened on
|
|
1525
|
+
`September 2026`, with no selected day in view.
|
|
1526
|
+
|
|
1527
|
+
Each mount now passes its first selected day as `defaultMonth`:
|
|
1528
|
+
|
|
1529
|
+
- `CalendarView`'s header date popover (`@object-ui/plugin-calendar`) opens on
|
|
1530
|
+
the date the header names, and follows Previous / Next between opens;
|
|
1531
|
+
- `ui:calendar` opens on its day in `single` mode, on the first valid listed
|
|
1532
|
+
day in `multiple` mode (an entry that is not a date is skipped) and on `from`
|
|
1533
|
+
in `range` mode. It reads that month once, when it mounts: a value that
|
|
1534
|
+
changes afterwards does not move the month it shows;
|
|
1535
|
+
- the form date picker (`date-picker`) and `DatePicker` open on their value;
|
|
1536
|
+
- the dashboard date filter's custom-range calendar
|
|
1537
|
+
(`@object-ui/plugin-dashboard`) opens on the range's `from`.
|
|
1538
|
+
|
|
1539
|
+
With no value, each still opens on today. react-day-picker throws on an
|
|
1540
|
+
invalid `defaultMonth`, so a day that names no instant is not passed as one: an
|
|
1541
|
+
unparseable `ui:calendar` day, a range `from` that does not parse, and an
|
|
1542
|
+
invalid `Date` a host hands `CalendarView` each open on today, as before. The
|
|
1543
|
+
two date pickers are unchanged in this respect: an invalid value already fails
|
|
1544
|
+
in their trigger's label, before any calendar mounts. The `Calendar` primitive
|
|
1545
|
+
itself is unchanged.
|
|
1546
|
+
- d425b8c: fix(plugin-dashboard): a `dashboard` block on a page loads its dataset widgets, and they re-read on the data-invalidation bus
|
|
1547
|
+
|
|
1548
|
+
`DashboardRenderer` read its adapter from its `dataSource` prop only. A page
|
|
1549
|
+
renders its blocks through `SchemaRenderer`, which passes no adapter prop, so a
|
|
1550
|
+
`dashboard` block held on a page sent no `queryDataset` call and every
|
|
1551
|
+
dataset-bound widget showed "This data source does not support dataset
|
|
1552
|
+
queries." although the page had a capable adapter. The renderer now resolves
|
|
1553
|
+
its adapter the way `object-grid`, `object-form` and `detail-view` do
|
|
1554
|
+
(`useResolvedDataSource`): the prop first, the `SchemaRendererProvider` context
|
|
1555
|
+
second. The widgets, the filter bar's `optionsFrom` read and the drill drawers
|
|
1556
|
+
all get that one adapter. A host with no capable adapter anywhere still sees
|
|
1557
|
+
the alert.
|
|
1558
|
+
|
|
1559
|
+
`DatasetWidget`'s query now also names the `useDataInvalidation` nonce for the
|
|
1560
|
+
dataset's base object (the `object` its answer names), so a write declared on
|
|
1561
|
+
the bus (`notifyDataChanged`) re-reads the tile in place. This holds on both
|
|
1562
|
+
dashboard surfaces. The current rows stay on screen under a refresh indicator
|
|
1563
|
+
during the re-read, not the loading skeleton. A widget whose adapter cannot run
|
|
1564
|
+
dataset queries subscribes to nothing.
|
|
1565
|
+
- fab627f: The dashboard date filter's "Custom…" item now opens the range calendar,
|
|
1566
|
+
including over a stored custom range, and the calendar stays open
|
|
1567
|
+
(objectui#10843).
|
|
1568
|
+
|
|
1569
|
+
Two defects, both measured in Chromium. With a custom range stored, the
|
|
1570
|
+
select's value already is the "Custom…" item, and Radix Select reports a pick
|
|
1571
|
+
only when it changes the value, so picking it again did nothing: a stored range
|
|
1572
|
+
could not be reopened for editing. From a preset, picking "Custom…" from the
|
|
1573
|
+
list did open the calendar, but the select then returned focus to its trigger,
|
|
1574
|
+
and focus leaving the calendar's popover dismissed it as soon as the select
|
|
1575
|
+
finished closing.
|
|
1576
|
+
|
|
1577
|
+
Picked from the list (pointer, touch, Enter or Space), "Custom…" now opens the
|
|
1578
|
+
calendar whether the value changes or not, and only after the list has closed,
|
|
1579
|
+
so the calendar keeps focus. Typing its first letter on the focused, closed
|
|
1580
|
+
select still opens the calendar when that changes the value (from a preset or
|
|
1581
|
+
"All time"), as it did before this change. A stored range reopens on the month
|
|
1582
|
+
of its first day. Closing the calendar with Escape returns focus to the date
|
|
1583
|
+
select. Closing the select without picking "Custom…" (Escape, a click outside)
|
|
1584
|
+
still opens nothing, and picking a preset commits it as before.
|
|
1585
|
+
- a60539b: fix(plugin-dashboard,components,plugin-report): three more read sites show the day a stored date-only value names, in every zone (objectui#10844)
|
|
1586
|
+
|
|
1587
|
+
Each read below parsed a date-only `YYYY-MM-DD` string with the engine's own `Date`
|
|
1588
|
+
parse, which reads it as UTC midnight. West of UTC that is the evening before, so
|
|
1589
|
+
each face showed the previous day. Each now reads the value through `toDisplayDate`
|
|
1590
|
+
(`@object-ui/core`), which rebuilds a date-only string at local midnight of the day it
|
|
1591
|
+
names. For a real day, a viewer in UTC or east of it sees no change.
|
|
1592
|
+
|
|
1593
|
+
- **Dashboard date filter, custom range.** The range calendar highlighted the day
|
|
1594
|
+
before each stored bound and could open on the previous month: clicking 15 Sep stored
|
|
1595
|
+
`2026-09-15` and then highlighted the 14th. It now highlights and opens on the stored
|
|
1596
|
+
days. A bound naming a day its month does not have (`2026-02-30`) no longer rolls
|
|
1597
|
+
into March: it selects no day, and a `from` like that opens the calendar on today, as
|
|
1598
|
+
an unparseable `from` already did.
|
|
1599
|
+
- **`date-picker` renderer, an ISO string `value`.** An authored or bound
|
|
1600
|
+
`value: '2024-01-15'` was labelled "January 14th, 2024" and selected the 14th. It now
|
|
1601
|
+
labels and selects the 15th. Only a date-only string naming a real day is read this
|
|
1602
|
+
way: a `Date`, a date-time string and any other string reach date-fns exactly as
|
|
1603
|
+
before. So an unparseable string still makes the trigger's `format` throw, as it did
|
|
1604
|
+
before, and a date-only string naming a nonexistent day is still rolled forward
|
|
1605
|
+
rather than refused, because refusing it would make `format` throw where it rendered
|
|
1606
|
+
before.
|
|
1607
|
+
- **Report cell date face (`formatValue`).** The `yyyy-MM-dd` face `ReportViewer` gives
|
|
1608
|
+
an untyped ISO-looking value and an aggregated column read `2026-09-14` for a stored
|
|
1609
|
+
`2026-09-15`. It now reads `2026-09-15`. A value naming a nonexistent day now renders
|
|
1610
|
+
as the raw stored string, this face's existing answer for an unparseable value,
|
|
1611
|
+
instead of a rolled-forward day.
|
|
1612
|
+
|
|
1613
|
+
A date-time value (one with a time part) still renders in the viewer's zone at every
|
|
1614
|
+
site.
|
|
1615
|
+
- 9f0c84a: fix(plugin-dashboard): a dashboard filter's `optionsFrom` options re-read on the data-invalidation bus
|
|
1616
|
+
|
|
1617
|
+
A `select` or `lookup` entry in a dashboard's `globalFilters` that reads its
|
|
1618
|
+
options through `optionsFrom` now re-reads them when the bus reports a change to
|
|
1619
|
+
`optionsFrom.object` (`notifyDataChanged`, as a page action over raw HTTP
|
|
1620
|
+
does), or an unscoped `'*'`, on either read: the server-side dataset GROUP BY
|
|
1621
|
+
and the client-side `find` fallback. A change to another object does not
|
|
1622
|
+
re-read. The options on screen stay until the re-read answers, and the value the
|
|
1623
|
+
user selected is kept: it is the dashboard variable's, and the read never writes
|
|
1624
|
+
it. Before, the options refreshed after such a write only when the host
|
|
1625
|
+
remounted the dashboard, and `PageView` is about to stop doing that
|
|
1626
|
+
(objectui#10519).
|
|
1627
|
+
|
|
1628
|
+
A filter with authored `options` and no `optionsFrom` reads nothing and does not
|
|
1629
|
+
subscribe.
|
|
1630
|
+
|
|
1631
|
+
**Clause-②: no** — no exported symbol, prop or authored key is added, removed,
|
|
1632
|
+
renamed or retyped, and no accept set moves. What changes is when an existing
|
|
1633
|
+
read runs.
|
|
1634
|
+
- ac15833: fix(data-objectstack, plugin-dashboard): a dataset tile the viewer may not read shows a localized "no access" state (objectui#10899)
|
|
1635
|
+
|
|
1636
|
+
When the analytics read admission refused a dashboard dataset query — `403`
|
|
1637
|
+
with ADR-0112 `PERMISSION_DENIED` — `queryDataset` had no branch for that code,
|
|
1638
|
+
so it threw the generic `Dataset query failed: 403 Forbidden — [Analytics]
|
|
1639
|
+
Access denied: …` string, and `DatasetWidget` printed it verbatim in a red
|
|
1640
|
+
alert. The list view over the same object already says 「无权访问」.
|
|
1641
|
+
|
|
1642
|
+
- `queryDataset` now throws a typed `AnalyticsForbiddenError` for that code,
|
|
1643
|
+
carrying `httpStatus: 403` and `code: 'PERMISSION_DENIED'` (plus the server's
|
|
1644
|
+
code and message for diagnostics). A code-less 403 — no ObjectStack route
|
|
1645
|
+
wrote it — keeps the generic error.
|
|
1646
|
+
- `DatasetWidget` classifies a failed query with the shared `classifyLoadError`
|
|
1647
|
+
and, for `forbidden`, renders a localized no-access state
|
|
1648
|
+
(`dashboard.widgetForbiddenTitle` / `dashboard.widgetForbiddenMessage`, new in
|
|
1649
|
+
all ten packs) instead of the exception text. Every other failure keeps the
|
|
1650
|
+
detailed alert.
|
|
1651
|
+
- 6466a09: fix(plugin-dashboard): the auto-refresh interval reads its handler through a ref, not a memoised identity
|
|
1652
|
+
|
|
1653
|
+
`useDashboardAutoRefresh`, the one timer behind `DashboardGridLayout` and
|
|
1654
|
+
`DashboardRenderer`, armed its interval in an effect keyed on
|
|
1655
|
+
`[seconds, onRefresh, handleRefresh]`, and `handleRefresh` is a `useCallback`
|
|
1656
|
+
result. AGENTS.md #10 bans exactly that: React may throw a memo away and hand
|
|
1657
|
+
back a new function while nothing it depends on changed, and every such
|
|
1658
|
+
identity change cleared the interval and set it again, restarting its phase. A
|
|
1659
|
+
host that passed a new `onRefresh` identity at the same period restarted it the
|
|
1660
|
+
same way.
|
|
1661
|
+
|
|
1662
|
+
The interval is now keyed on the two values it reads, the period and whether a
|
|
1663
|
+
handler is wired. The handler is read through a ref when a run happens, so the
|
|
1664
|
+
run still calls the CURRENT `onRefresh`, and a new handler identity, from the
|
|
1665
|
+
host or from a discarded memo, leaves the phase where it was. The manual
|
|
1666
|
+
"Refresh All" button reads the same ref.
|
|
1667
|
+
|
|
1668
|
+
The existing timer pin keeps every case green, the equal-period phase case and
|
|
1669
|
+
the handler-swap case among them, and adds one case per surface that forces a
|
|
1670
|
+
memo discard mid-period and then swaps the host handler mid-period, asserting
|
|
1671
|
+
both times that the run still lands on the original schedule.
|
|
1672
|
+
- f61dab1: `reference` is now the only spelling ObjectUI writes or reads for a relational field's target object (objectui#11070, round 4, under the objectui#6837 ruling: 「objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀」).
|
|
1673
|
+
|
|
1674
|
+
- **Types.** `LookupFieldMetadata`, `MasterDetailFieldMetadata` and `DetailViewField` (with its zod mirror `DetailViewFieldSchema`) declare `reference` and no longer declare `reference_to`. On the two field metadata types the member is typed by reference to `@objectstack/spec`'s `FieldSchema.reference`. `@object-ui/plugin-form`'s `FieldDefaultsSchemaLike` drops its `reference_to` member the same way.
|
|
1675
|
+
- **Readers.** `LookupField`, `UserField`, `LookupCellRenderer`, `UserCellRenderer`, the inline editor's reference fallback, the form's `current_user` seeding and the inline-subform parent lookup read `reference` alone.
|
|
1676
|
+
- **Emitters.** Every in-repo producer that builds a field definition or a widget `field` prop writes `reference`: the action-param dialog (`paramToField`), the bulk-action dialog, the record detail page, drawer, footer, related list and synthesised page, the gallery card, the form's section-field override and the flow designer's reference picker. The grid's and the dashboard's relational copy sets carry `reference` and no longer copy `reference_to`.
|
|
1677
|
+
- **Ingestion.** `normalizeFieldReferenceKeys` (behind `ObjectStackAdapter.getObjectSchema` and `MetadataProvider`) still folds a legacy `reference_to` / `referenceTo` onto `reference` when `reference` is absent, and still warns in dev. It no longer stamps `reference_to` onto every relational definition, and it still never drops or overwrites a key.
|
|
1678
|
+
|
|
1679
|
+
`@objectstack/spec`'s `FieldSchema` refuses `reference_to` by name, and objectstack#13847 rewrites stored ones on the serve path and in `os migrate meta`, so a definition served by an ObjectStack backend is unaffected.
|
|
1680
|
+
|
|
1681
|
+
## ⚠️ BREAKING for a host that hands `reference_to` to the widgets directly
|
|
1682
|
+
|
|
1683
|
+
The type change is a compile error for TypeScript that writes `reference_to` on `LookupFieldMetadata`, `MasterDetailFieldMetadata` or `DetailViewField`: rename it to `reference`.
|
|
1684
|
+
|
|
1685
|
+
At runtime the break reaches exactly one kind of host: one that serves object definitions spelling the target only as `reference_to` through a `DataSource` other than `ObjectStackAdapter`, or that passes such a definition straight into `LookupField`, `UserField` or a cell renderer. Those definitions never pass the ingestion fold. Measured with an object-bound lookup field on `ObjectForm` and a lookup column on `ObjectGrid`, both fed by a hand-written `DataSource` whose `getObjectSchema` returns `{ type: 'lookup', reference_to: 'account' }`:
|
|
1686
|
+
|
|
1687
|
+
- before this change, opening the picker queried `account` and the cell resolved the record's name;
|
|
1688
|
+
- after it, the picker has no object to query (no `find` call is made) and the cell shows the raw id beside the unresolved-reference marker.
|
|
1689
|
+
|
|
1690
|
+
The same definition served through `ObjectStackAdapter.getObjectSchema` still works, before and after: the fold adds `reference`, the picker queries `account`, the cell resolves the name, and the dev warning names the field. **Fix:** spell the target `reference` in the definition your `DataSource` serves.
|
|
1691
|
+
|
|
1692
|
+
⚠️ **Dated note, 2026-09-30 — the break reaches more than the paragraph above states — objectui#11070.** Two corrections from the contract review of this change, appended rather than edited in; neither changes what ships.
|
|
1693
|
+
|
|
1694
|
+
- "The break reaches exactly one kind of host" above is too narrow. Host code that read `reference_to` off a definition after ingestion — from `ObjectStackAdapter.getObjectSchema()` or `useMetadata().objects` — also loses that key, because the ingestion pass no longer stamps it (the **Ingestion** bullet above states the fact, and `reference_to` was never a declared member of those definitions). **Fix:** read `reference`.
|
|
1695
|
+
- The measured break above is a `lookup` field's. A `user` field handed a `reference_to`-only definition directly does not end at "no query": `UserField` falls back to `sys_user`, so its picker queries `sys_user` rather than the object `reference_to` named. **Fix:** spell the target `reference` there too.
|
|
1696
|
+
|
|
1697
|
+
The text above is kept as the reading of this change.
|
|
1698
|
+
- dd5ff19: The text-family field types and every length reader use `@objectstack/spec`'s own `minLength` / `maxLength`, and the snake_case `min_length` / `max_length` are retired at once, with no alias (objectui#11070, the text-family round). Two switches nothing read, `auto_compute` and `auto_update`, are retired too.
|
|
1699
|
+
|
|
1700
|
+
- **Types.** `TextFieldMetadata` and `TextareaFieldMetadata` declare `minLength` and `maxLength`; `MarkdownFieldMetadata`, `HtmlFieldMetadata`, `RichtextFieldMetadata`, `EmailFieldMetadata` and `UrlFieldMetadata` declare `maxLength`. Each is `FieldSchema`'s member by reference, and each replaces the snake_case member the type declared before. `FormulaFieldMetadata.auto_compute` and `SummaryFieldMetadata.auto_update` are removed: nothing in ObjectUI read either.
|
|
1701
|
+
- **Readers.** The form renderer's built-in `input`, `textarea` and fallback branches, the form's validation rules (`buildValidationRules`), `TextAreaField`, `RichTextField`, `EmbeddableForm`'s default long-text cap and `ObjectForm`'s length forwarding read `maxLength` / `minLength` alone. The built-in branches no longer strip a `max_length` key off the element: nothing reads it, so it is treated like any other undeclared key.
|
|
1702
|
+
- **Dashboard.** `ObjectDataTable` and `RecordDetailDrawer` no longer copy a lookup's `display_field` onto their internal cell meta. The lookup cell has read the display pointer as `displayField` since objectui#7155, so nothing changes on screen.
|
|
1703
|
+
|
|
1704
|
+
`FieldSchema` refuses `min_length`, `max_length`, `auto_compute` and `auto_update` by name, so no spec-compliant producer writes them. A definition served through `ObjectStackAdapter.getObjectSchema` or `MetadataProvider` is unaffected even if it was stored with a snake_case length: the ingestion pass folds `max_length` / `min_length` onto `maxLength` / `minLength` before any reader sees it. Measured with an object-bound `textarea` field declaring `max_length: 111` on `ObjectForm`, served through `ObjectStackAdapter`: the editor has `maxlength="111"` and a `0/111` counter before and after this change.
|
|
1705
|
+
|
|
1706
|
+
## ⚠️ BREAKING, priced as minor under the fixed group's version policy
|
|
1707
|
+
|
|
1708
|
+
The type change is a compile error for TypeScript that writes `min_length`, `max_length`, `auto_compute` or `auto_update` on one of these types (an excess-property error naming the key). Rename the first two to `minLength` / `maxLength`, and delete the other two.
|
|
1709
|
+
|
|
1710
|
+
At runtime, a length spelled only `max_length` or `min_length` now applies nowhere the ingestion fold does not run first. Measured before and after this change:
|
|
1711
|
+
|
|
1712
|
+
- **A hand-authored `form` document handed straight to the renderer.** A built-in `input` field with `max_length: 5` and a `textarea` field with `max_length: 7` rendered `maxlength="5"` and `maxlength="7"`. They now render no `maxlength`, and the element carries `max_length` as an inert attribute, as it would any key the renderer does not read. The same fields spelled `maxLength` render identically before and after.
|
|
1713
|
+
- **An object definition served to `ObjectForm` by a `DataSource` other than `ObjectStackAdapter`.** A `textarea` field with `max_length: 111` had `maxlength="111"` and a `0/111` counter; it now has neither. The same field spelled `maxLength` is unchanged.
|
|
1714
|
+
- **Submit-time validation.** `buildValidationRules` on a field carrying only `max_length: 9` / `min_length: 2` produced `maxLength` and `minLength` rules; it now produces no rules.
|
|
1715
|
+
- **`EmbeddableForm`.** A custom `textarea` field carrying `max_length: 40` used to opt out of the default cap and got no `maxLength`; it now gets the 5000-character long-text default.
|
|
1716
|
+
|
|
1717
|
+
**Fix:** spell the bounds `maxLength` / `minLength`.
|
|
1718
|
+
- cff8641: The dashboard's refresh button now speaks the session language (objectui#11097). `DashboardRenderer` and `DashboardGridLayout` hard-coded "Refresh All", "Refreshing…" and the accessible name "Refresh dashboard", so a zh-CN session read English on the button that every console dashboard shows since the console began wiring `onRefresh`. Both components now read the pack keys `dashboard.refreshAll`, `dashboard.refreshDashboard` and the existing `dashboard.refreshing`. The English wording is unchanged.
|
|
1719
|
+
|
|
1720
|
+
`@object-ui/i18n`: new keys `dashboard.refreshAll` and `dashboard.refreshDashboard` in every built-in locale pack.
|
|
1721
|
+
- b149617: fix(plugin-dashboard): the `object-metric` tile shows a percent or number aggregate at the field's declared width
|
|
1722
|
+
|
|
1723
|
+
A metric tile over a `percent` or `number` field picked its pattern from the
|
|
1724
|
+
field's type alone, so it never showed the decimals the field declares. A
|
|
1725
|
+
`number` field declaring `scale: 2` whose average is 3.75 read `4`, and a
|
|
1726
|
+
`percent` field declaring `scale: 2` read `12%` beside list cells reading
|
|
1727
|
+
`12.34%`.
|
|
1728
|
+
|
|
1729
|
+
The tile now reads the field's width through `resolveFieldScale` from
|
|
1730
|
+
`@objectstack/spec/data`, the same answer the list cell, the record header chip,
|
|
1731
|
+
the grid footer and the percent edit widget use:
|
|
1732
|
+
|
|
1733
|
+
- a declared `scale` shows as declared: `3.75`, `12.34%`, and `scale: 3` pads to
|
|
1734
|
+
`1.500`;
|
|
1735
|
+
- a `percent` that declares nothing keeps whole percents, the protocol's width;
|
|
1736
|
+
- a `min` or `max` over a `number` that declares nothing shows the value at its
|
|
1737
|
+
own decimals (`0.99`, not `1`), since that value is one of the records' own
|
|
1738
|
+
values.
|
|
1739
|
+
|
|
1740
|
+
A `sum` or `avg` over a `number` that declares no `scale` keeps its
|
|
1741
|
+
whole-number display: the tile does not see the values the server aggregated,
|
|
1742
|
+
so their widths are not known to it. An authored `format` on the widget still
|
|
1743
|
+
wins, and a `count` is still a plain number of rows.
|
|
1744
|
+
|
|
1745
|
+
`@object-ui/plugin-dashboard` now depends on `@objectstack/spec` at runtime,
|
|
1746
|
+
from `^17.5.0`, the first release exporting `resolveFieldScale`.
|
|
1747
|
+
- b4333ab: fix(plugin-dashboard): a dimensionless table renders a row of every measure, and a dimensionless chart one mark per measure
|
|
1748
|
+
|
|
1749
|
+
`DatasetWidget` treated every widget that declares no `dimensions` as a
|
|
1750
|
+
one-number tile. So `{ type: 'table', dataset: 'sales', values: ['revenue', 'cost', 'margin'] }`
|
|
1751
|
+
rendered `revenue` alone, and `cost` and `margin` were queried and never
|
|
1752
|
+
shown. Every door accepts that widget, and the spec's metric-family arity
|
|
1753
|
+
refusal sends authors to it: "`type: 'table'` renders a row of measures, and
|
|
1754
|
+
the chart families (`bar` / `line` / `area` / `combo`) render one mark per
|
|
1755
|
+
measure".
|
|
1756
|
+
|
|
1757
|
+
Only a metric-family type is now a tile by type. With no dimension and two or
|
|
1758
|
+
more measures:
|
|
1759
|
+
|
|
1760
|
+
- a `table` / `pivot` renders one row carrying every measure, with no totals
|
|
1761
|
+
footer (that one row is the grand total);
|
|
1762
|
+
- a `bar` / `line` / `area` / `combo` chart plots one mark per measure, with the
|
|
1763
|
+
measures' labels on the category axis, under one series labelled with the
|
|
1764
|
+
existing `dashboard.total` string. A comparison the widget asks for is drawn
|
|
1765
|
+
beside each measure.
|
|
1766
|
+
|
|
1767
|
+
Unchanged: a widget with one measure and no dimension is still a tile on every
|
|
1768
|
+
type, since nothing is dropped. A dimensionless widget of a type the spec's
|
|
1769
|
+
text does not name (`pie`, `donut`, `funnel`, `scatter`, `column`,
|
|
1770
|
+
`horizontal-bar`, `radar`, `treemap`, `sankey`) also keeps its tile. The query
|
|
1771
|
+
still selects exactly the declared measures.
|
|
1772
|
+
- 6e9c8d2: fix: the widget width / height editors write a whole four-number `layout` (objectui#11388)
|
|
1773
|
+
|
|
1774
|
+
The three dashboard widget width / height editors (Studio's widget inspector in
|
|
1775
|
+
`@object-ui/app-shell`, `DashboardWithConfig` in `@object-ui/plugin-dashboard` and
|
|
1776
|
+
`DashboardEditor` in `@object-ui/plugin-designer`) each spread the one edited
|
|
1777
|
+
dimension onto the widget's possibly-absent `layout` and cast the result past the
|
|
1778
|
+
declared four-number type. On a widget with no `layout`, which is how Studio adds
|
|
1779
|
+
one, they stored `{ w }` or `{ h }`, and `@objectstack/spec`'s widget `layout`
|
|
1780
|
+
refuses that box at `x`, `y` and the other dimension.
|
|
1781
|
+
|
|
1782
|
+
`@object-ui/types` gains two exports on its main entry:
|
|
1783
|
+
|
|
1784
|
+
- `defaultWidgetPlacement(index)`: the box the dashboard grid auto-places the
|
|
1785
|
+
widget at that position of `widgets[]` in when it has no `layout`, the fallback
|
|
1786
|
+
the spec's widget `layout` states (`x: (i % 4) * 3`, `y: Math.floor(i / 4) * 4`,
|
|
1787
|
+
`w: 3`, `h: 4`).
|
|
1788
|
+
- `completeWidgetLayout(layout, patch, placement)`: the whole box after an edit,
|
|
1789
|
+
each coordinate taken from the edit, else from the stored `layout`, else from
|
|
1790
|
+
the placement.
|
|
1791
|
+
|
|
1792
|
+
All three editors now write through `completeWidgetLayout`, seeding from
|
|
1793
|
+
`defaultWidgetPlacement` for the widget's index, and the casts are gone, so the
|
|
1794
|
+
compiler judges each write. A widget that has a `layout` keeps its `x` and `y`.
|
|
1795
|
+
The width and height inputs (and `DashboardWithConfig`'s sliders) show the same
|
|
1796
|
+
completed box, so a widget with no `layout` now shows the auto-placed 3 × 4 where
|
|
1797
|
+
it showed 1 × 1, and the dimension an edit leaves alone is the one on screen.
|
|
1798
|
+
`DashboardGridLayout` places a widget with no `layout` through
|
|
1799
|
+
`defaultWidgetPlacement`, with no change to where it lands.
|
|
1800
|
+
|
|
1801
|
+
`DashboardWithConfig`'s `onWidgetSave` payload keeps its flattened `layoutW` /
|
|
1802
|
+
`layoutH` shape. For a widget with no `layout` that the author did not resize,
|
|
1803
|
+
the two values it carries are now the auto-placed 3 and 4, not 1 and 1.
|
|
1804
|
+
- 175df47: fix(plugin-dashboard): a dimensioned `pie` / `donut` / `funnel` / `treemap` / `sankey` widget with several measures now says which measures it drops (objectui#11417)
|
|
1805
|
+
|
|
1806
|
+
A dataset-bound chart widget hands every measure it declares to the shared chart
|
|
1807
|
+
renderer as a series. On the `pie` / `donut`, `funnel`, `treemap` and `sankey` families
|
|
1808
|
+
the renderer draws the first series and no other, so a widget such as
|
|
1809
|
+
`{ type: 'pie', dataset, dimensions: ['stage'], values: ['revenue', 'cost'] }` drew
|
|
1810
|
+
revenue per stage and nothing of cost. The query computed both measures, every door
|
|
1811
|
+
accepted the document, and nothing said that cost was dropped.
|
|
1812
|
+
|
|
1813
|
+
`DatasetWidget`'s dropped-measure warning (objectui#8894) now covers that shape. It names
|
|
1814
|
+
the widget, the measure it renders and the measures it queried and never displayed, and
|
|
1815
|
+
says that the chart family draws a single series, the first declared measure. It points
|
|
1816
|
+
to no ADR-0087 entry for this shape: the metric-family entry the tile warning points to
|
|
1817
|
+
answers for a one-number tile, and a chart with a dimension is not one. A `pyramid`
|
|
1818
|
+
widget, which renders as a funnel, is covered too.
|
|
1819
|
+
|
|
1820
|
+
**Nothing is drawn differently.** The chart still receives every measure and draws what
|
|
1821
|
+
it drew before. A single measure on these charts stays silent, and so does every chart
|
|
1822
|
+
that draws each measure (`bar`, `line`, `area`, `combo`, `radar`, and so on). A `scatter`
|
|
1823
|
+
with several measures is unchanged: the chart already shows a notice naming the series
|
|
1824
|
+
it cannot plot.
|
|
1825
|
+
|
|
1826
|
+
The spec is to refuse two or more measures on these five types with a dimension
|
|
1827
|
+
(objectstack#21293); until objectui's pin carries that refusal, every door accepts the
|
|
1828
|
+
shape and this warning is the signal.
|
|
1829
|
+
- 9d9ed54: The drill-down drawer renders a `drillDown.report` as a `report` node, `{ type: 'report', report }` (objectui#11440).
|
|
1830
|
+
|
|
1831
|
+
It used to render a `spec-report` node, the alias `@object-ui/plugin-report` retires in this same release. The drilled report still carries the widget's resolved filter in its `filter` member, joined to the report's own `filter` with `$and` when it has one; a dataset-bound report does not apply that member (objectui#5137), which this release does not change (objectui#11506). What the drawer draws is unchanged, provided `@object-ui/plugin-report` is at this release too: an older `ReportRenderer` unwraps the `report` member only on `spec-report`. Nothing authored changes. The README's `object-pivot` row no longer calls the block an internal wrapper: it is a public block whose props go in `properties`.
|
|
1832
|
+
|
|
1833
|
+
⚠️ **Dated note, 2026-10-02 — the drilled report is scoped by `runtimeFilter` — objectui#11506.**
|
|
1834
|
+
Later in this same release the drawer writes the widget's resolved filter into
|
|
1835
|
+
a dataset-bound report as `runtimeFilter`, joined to the report's own
|
|
1836
|
+
`runtimeFilter` with `$and` when it has one, and the dataset renderer applies
|
|
1837
|
+
it: a drill from a filtered widget into a dataset report queries the slice that
|
|
1838
|
+
was clicked. The sentence above that says the drilled report carries the filter
|
|
1839
|
+
in its `filter` member, which a dataset-bound report does not apply and which
|
|
1840
|
+
"this release does not change", no longer describes the drawer. The same change
|
|
1841
|
+
retires the pre-9.0 `objectName` drill report, and the drawer now recognises a
|
|
1842
|
+
report by its `dataset`. The rest of this entry is kept as the reading of this change.
|
|
1843
|
+
- 6007dd4: fix(fields): a read-only percent or currency field shows its value the way its table cell does, and a whole currency amount keeps its minor units (objectui#11444)
|
|
1844
|
+
|
|
1845
|
+
A read-only `PercentField` printed the percentage with a literal `%` and no display
|
|
1846
|
+
locale. A field declaring `scale: 2` and holding `0.25` read `25.00%` in a German
|
|
1847
|
+
read-only form while its table cell read `25,00 %`, and a four-digit percentage lost
|
|
1848
|
+
its grouping (`1234.5%` against the cell's `1,234.5%`). The read-only face now renders
|
|
1849
|
+
through the same call the cell's `formatPercent` uses, so the locale's percent
|
|
1850
|
+
convention, its grouping and the declared width match. The width is still the one
|
|
1851
|
+
`resolveFieldScale` answers, and the face still reads a value as a fraction unless the
|
|
1852
|
+
field declares `max > 1`.
|
|
1853
|
+
|
|
1854
|
+
A read-only `CurrencyField` and `CurrencyCellRenderer` disagreed on a whole amount: the
|
|
1855
|
+
form read `$3,456.00` while the cell read `$3,456`. Triage ruled that the declared
|
|
1856
|
+
width, a currency's ISO 4217 minor-unit count, is the protocol's convention, and retired
|
|
1857
|
+
the whole-amount trimming `formatCurrency` applied since objectui#4033 (objectui#11444,
|
|
1858
|
+
comment 5946462862). The read-only face now calls `formatCurrency` itself.
|
|
1859
|
+
|
|
1860
|
+
**Visible change in table cells.** `formatCurrency` no longer drops the fraction of a
|
|
1861
|
+
whole amount. Every face that formats through it moves with it: the currency table
|
|
1862
|
+
cell, the record detail panel, the grid column-summary footer, the dashboard metric
|
|
1863
|
+
tile and table widget, and the gantt tooltip. A whole USD amount reads `$3,456.00`
|
|
1864
|
+
instead of `$3,456`, a whole KWD amount `KWD 3,456.000` instead of `KWD 3,456`, and an
|
|
1865
|
+
amount with no currency resolved `3,456.00` instead of `3,456`. A currency with no
|
|
1866
|
+
minor unit is unchanged: a whole JPY amount still reads `¥3,456`. Fractional amounts
|
|
1867
|
+
render exactly as before. The dashboard metric tile restated the cell's no-currency
|
|
1868
|
+
width for a field with no currency resolved (no decimals for a whole amount); it now
|
|
1869
|
+
restates two decimals, so a whole amount reads `3,456.00` on the tile as in the cell.
|
|
1870
|
+
|
|
1871
|
+
`formatCurrency` keeps its name, its signature and its `@object-ui/fields` export.
|
|
1872
|
+
Nothing changes in the editable inputs.
|
|
1873
|
+
- 8bfc001: fix(plugin-dashboard): an `object-metric` with a structured `groupBy` and a rule-list `filter` draws its number
|
|
1874
|
+
|
|
1875
|
+
An authored node such as
|
|
1876
|
+
|
|
1877
|
+
```ts
|
|
1878
|
+
{
|
|
1879
|
+
type: 'object-metric',
|
|
1880
|
+
properties: {
|
|
1881
|
+
objectName: 'opportunity',
|
|
1882
|
+
aggregate: { field: 'amount', function: 'sum', groupBy: { field: 'close_date', dateGranularity: 'month' } },
|
|
1883
|
+
filter: [{ field: 'stage', operator: 'equals', value: 'won' }],
|
|
1884
|
+
},
|
|
1885
|
+
}
|
|
1886
|
+
```
|
|
1887
|
+
|
|
1888
|
+
passes both authoring faces (`safeValidateSchema`, which `objectui validate` runs, and
|
|
1889
|
+
`StrictAnyComponentSchema`), and on `ObjectStackAdapter` it drew the adapter's refusal
|
|
1890
|
+
("the spec-shape branch received a `where` array that is not a filter") instead of its number
|
|
1891
|
+
(objectui#11526).
|
|
1892
|
+
|
|
1893
|
+
A structured `groupBy` sends the tile down the spec-shape aggregate query (objectui#8613), and
|
|
1894
|
+
`ObjectMetricWidget` passed the bag's `filter`, a `ViewFilterRule[]`, into that query's `where`
|
|
1895
|
+
as written. The adapter posts that `where` verbatim and refuses a rule list there with
|
|
1896
|
+
`UnloweredAggregateWhereError` (objectui#6825), so the query was never sent. The same rule list
|
|
1897
|
+
with a string `groupBy` drew its number, because the legacy aggregate bag carries it as
|
|
1898
|
+
`filter` and the adapter lowers that key itself.
|
|
1899
|
+
|
|
1900
|
+
The tile now lowers the rule list before it builds the spec-shape query: `toFilterNode` from
|
|
1901
|
+
`@object-ui/core` turns it into filter AST, and `parseFilterAST` from `@objectstack/spec/data`
|
|
1902
|
+
turns that into the `FilterCondition` that `QuerySchema.where` declares. These are the same two
|
|
1903
|
+
stages the adapter applies to the rule list on the legacy bag, where its own translator is
|
|
1904
|
+
private to `@object-ui/data-objectstack`. An array that is neither a rule list nor filter AST is
|
|
1905
|
+
passed on unparsed, so the adapter still refuses it rather than receiving no filter and
|
|
1906
|
+
aggregating every row.
|
|
1907
|
+
|
|
1908
|
+
What does not move:
|
|
1909
|
+
|
|
1910
|
+
- a string or absent `groupBy`: the legacy bag still carries the rule list as `filter`, and the
|
|
1911
|
+
adapter lowers it as before;
|
|
1912
|
+
- a flat record filter (`{ stage: 'won' }`): it is already a `FilterCondition` and still travels
|
|
1913
|
+
verbatim as `where`;
|
|
1914
|
+
- the drill-down drawer: it still receives the tile's resolved filter, unchanged;
|
|
1915
|
+
- `UnloweredAggregateWhereError`: the adapter still refuses an unlowered rule list from any
|
|
1916
|
+
caller that skips the lowering.
|
|
1917
|
+
|
|
1918
|
+
One posted body changes without changing its answer: an empty rule list on the spec-shape query
|
|
1919
|
+
now sends no `where`. It used to send `where: []`, which the platform engine reads as no filter.
|
|
1920
|
+
- baac3f4: Fix four `find()` calls that passed a query option without its `$`, and gate the shape.
|
|
1921
|
+
|
|
1922
|
+
`QueryParams` declares every query option `$`-prefixed and `convertQueryParams` copies
|
|
1923
|
+
exactly those keys, so an unprefixed spelling reaches no branch and is dropped — no throw,
|
|
1924
|
+
no warning, and it type-checks because the type carries `[key: string]: any` for
|
|
1925
|
+
adapter-specific params. For a dropped cap the result is an **unbounded** read rather than
|
|
1926
|
+
a truncated one: the platform's GET list route has no default page size, so the query
|
|
1927
|
+
returns the whole match set and stays invisible until the object is large.
|
|
1928
|
+
|
|
1929
|
+
- `app-shell` `ObjectView` fetched the footer's record count with `{ limit: 0 }`. This one
|
|
1930
|
+
**inverted** rather than widened — `$top: 0` is honoured end to end as "no records", so
|
|
1931
|
+
the dropped key turned "count only, fetch nothing" into "fetch every row in the object",
|
|
1932
|
+
on every mount and every refresh of every list view. It now sends `$top: 0` and reads
|
|
1933
|
+
the count off `total` only; the row-counting fallbacks are gone rather than repointed,
|
|
1934
|
+
because once zero rows are requested an empty `data` means "you asked for none", not
|
|
1935
|
+
"the object is empty", and counting it would assert a confident `0`. With no total the
|
|
1936
|
+
footer line is omitted instead.
|
|
1937
|
+
- `app-shell` `AssignedUsersSection` looked a permission set up with `{ …, limit: 1 }`,
|
|
1938
|
+
one line from three correct `$top` calls.
|
|
1939
|
+
- `plugin-dashboard` `DashboardFilterBar` passed `fields` **and** `top` in one literal, so
|
|
1940
|
+
a filter's option list read every row and every column of its source object while its
|
|
1941
|
+
own comment described it as capped at 200. The same call read `records.items`, which is
|
|
1942
|
+
not a `QueryResult` member, so against a real adapter the fallback produced no options
|
|
1943
|
+
at all.
|
|
1944
|
+
- `console` `sdui-workbench-preview` passed `{ top: 200 }` and read `.records` off the
|
|
1945
|
+
result in its page-source metadata.
|
|
1946
|
+
|
|
1947
|
+
A new `object-ui/no-unprefixed-query-params` ESLint rule rejects the shape at write time:
|
|
1948
|
+
a known query-option name missing its `$` in the second argument of a `find`/`findOne`
|
|
1949
|
+
call. It is narrow on purpose — a closed list of spellings, anchored to the call — because
|
|
1950
|
+
the index signature exists so adapters can take adapter-specific params, and a rule that
|
|
1951
|
+
flagged any unprefixed key would report the shape the type was written to allow. Its
|
|
1952
|
+
sibling `no-query-params-under-options` (the `{ options: { $top } }` half) is unchanged.
|
|
1953
|
+
- 39f4309: Published typings from every `vite-plugin-dts` package now carry an explicit extension on
|
|
1954
|
+
every relative specifier, and a type error in the declaration build now fails the build
|
|
1955
|
+
instead of being printed and ignored (objectui#5439, objectui#5483).
|
|
1956
|
+
|
|
1957
|
+
**Consumers on `moduleResolution: nodenext` or `node16` may see NEW type errors, and that
|
|
1958
|
+
is the fix working.** These packages re-export mostly through NAMED re-exports —
|
|
1959
|
+
`export { useObjectChat } from './useObjectChat'`. TypeScript could not follow the
|
|
1960
|
+
extensionless hop, but it still DECLARED the name, so the symbol resolved to a silent
|
|
1961
|
+
`any`. Nothing errored; consumers simply got no types. With the extension emitted, the
|
|
1962
|
+
symbol carries its real type, and any call site that was relying on the `any` now type
|
|
1963
|
+
checks for the first time. This is the mode that produced the 21 residual `TS7006` on
|
|
1964
|
+
`@object-ui/app-shell` reported against objectui#5365 — a type hole that opened quietly,
|
|
1965
|
+
unlike objectui#5365's own `export * from './ui'` packages where the same defect surfaced
|
|
1966
|
+
immediately as `TS2305: has no exported member`.
|
|
1967
|
+
|
|
1968
|
+
410 extensionless relative specifiers across 19 packages were emitted before this change;
|
|
1969
|
+
the count is now 0 in all 22 packages that build typings through `vite-plugin-dts`.
|
|
1970
|
+
`@object-ui/fields` was already clean — its sources write explicit `.js` specifiers — and
|
|
1971
|
+
is wired so it stays that way.
|
|
1972
|
+
|
|
1973
|
+
The second half changes no emitted output today: 22/22 packages built green unmodified, so
|
|
1974
|
+
making the declaration step's exit code honest turns nothing red. It changes what a FUTURE
|
|
1975
|
+
regression does — print and exit 0, versus fail the build.
|
|
1976
|
+
- fc62bb4: `TableColumn.type` now has ONE canonical value set across all three ends that disagreed
|
|
1977
|
+
(maintainer ruling 2026-08-25, Option B: the 8-literal interface union is
|
|
1978
|
+
canonical). The interface declared `'text' | 'number' | 'date' | 'datetime' | 'currency' |
|
|
1979
|
+
'percent' | 'boolean' | 'action'`; the zod mirror declared `z.string()` and accepted
|
|
1980
|
+
anything; the renderer branched on a third set and could only read the key through an
|
|
1981
|
+
`as any` cast.
|
|
1982
|
+
|
|
1983
|
+
## ⚠️ Accept-set narrowing — these spellings stop validating
|
|
1984
|
+
|
|
1985
|
+
`TableColumnSchema.type` was `z.string().optional()`. **Any string parsed green.** It is now
|
|
1986
|
+
`z.enum(TABLE_COLUMN_TYPES).optional()`, so a value outside the eight is refused at parse
|
|
1987
|
+
time with `type` named in the error path. Spellings that validated before and are **refused
|
|
1988
|
+
now**, grouped by why they were being written:
|
|
1989
|
+
|
|
1990
|
+
- **Typos and invented names** — `'money'`, `'datetime2'`, `'string'`, `'int'`, `'integer'`,
|
|
1991
|
+
`'float'`, `'double'`, `'datetime-local'`, and every other free-form string. `'money'` is
|
|
1992
|
+
the card's headline case: it validated, matched no renderer branch, and the column fell
|
|
1993
|
+
through to plain text rendering with nothing reported. That silent fall-through is the
|
|
1994
|
+
lenient-validation face that lets AI-authored metadata errors through, and it is now a
|
|
1995
|
+
loud parse failure.
|
|
1996
|
+
- **Object-schema field types written into a column slot** — `'select'`, `'lookup'`,
|
|
1997
|
+
`'user'`, `'file'`, `'formula'`, `'textarea'`, `'email'` and the other 35 members of
|
|
1998
|
+
`@objectstack/spec`'s `FieldType` that are not among the eight. These belong on the FIELD,
|
|
1999
|
+
not on the column: a column gets its dedicated widget from the field definition behind its
|
|
2000
|
+
`accessorKey`, never from `type`.
|
|
2001
|
+
|
|
2002
|
+
**Authored metadata in this repo needs no migration.** Measured before tightening, across
|
|
2003
|
+
`examples/`, `content/`, `apps/`, `e2e/`, `docs/` and every package (591 JSON schema files
|
|
2004
|
+
plus the docs and playground sources): **zero** authored `TableColumn.type` values outside
|
|
2005
|
+
the eight, and zero occurrences of `int` / `integer` / `float` / `double` in a column
|
|
2006
|
+
position anywhere in the repository. If you author `type` on a table column, check it
|
|
2007
|
+
against the eight; if the value describes the FIELD rather than the column, remove it.
|
|
2008
|
+
|
|
2009
|
+
## The renderer's undeclared vocabulary disappears instead of being declared
|
|
2010
|
+
|
|
2011
|
+
`int` / `integer` / `float` / `double` were members of the data-table's `NUMERIC_EDIT_TYPES`
|
|
2012
|
+
and `datetime-local` had its own editor branch, none of them declared. They arrived because
|
|
2013
|
+
column-inference producers forwarded an object schema's field type **verbatim** into
|
|
2014
|
+
`TableColumn.type`. Rather than publishing that dialect, producers now fold their inferred
|
|
2015
|
+
value onto the declared vocabulary at their emit seam via the new
|
|
2016
|
+
`normalizeTableColumnType()`: `int`/`integer`/`float`/`double` → `number`,
|
|
2017
|
+
`datetime-local` → `datetime`, and **anything else drops the `type` annotation — never the
|
|
2018
|
+
column**. Two producers do this, not the one the card named: `ObjectGrid` (`@object-ui/plugin-grid`)
|
|
2019
|
+
and `ObjectDataTable` (`@object-ui/plugin-dashboard`), whose `buildFieldMeta` spread wrote
|
|
2020
|
+
the raw field type into the same slot.
|
|
2021
|
+
|
|
2022
|
+
Dropping the annotation is behaviour-preserving at the only consumer that reads the key.
|
|
2023
|
+
`data-table`'s inline editor branches on `date`, `datetime` and the numeric set and
|
|
2024
|
+
otherwise falls through to a text input — which is exactly the `undefined` path. The
|
|
2025
|
+
dedicated widget a `select` or `lookup` column gets comes from the host's `renderCellEditor`,
|
|
2026
|
+
which resolves the field through `column.accessorKey` and never reads `type`.
|
|
2027
|
+
|
|
2028
|
+
## New public API
|
|
2029
|
+
|
|
2030
|
+
`@object-ui/types` exports `TABLE_COLUMN_TYPES` (the canonical tuple — the single
|
|
2031
|
+
declaration the zod mirror builds its enum from, so the two cannot drift), the
|
|
2032
|
+
`TableColumnType` union, and `normalizeTableColumnType()` for producers. The `as any` cast
|
|
2033
|
+
in `data-table.tsx` is deleted and the read is typed, so re-introducing an undeclared
|
|
2034
|
+
spelling is a tsc error rather than a silent widening.
|
|
2035
|
+
|
|
2036
|
+
A value-level parity pin covers all three ends
|
|
2037
|
+
(`packages/types/src/__tests__/table-column-type-canonical.test.ts` and
|
|
2038
|
+
`packages/components/src/renderers/complex/__tests__/table-column-type-read-set.test.tsx`).
|
|
2039
|
+
objectui#5684's guard is key-set only and cannot see value drift — `type` was present on
|
|
2040
|
+
both sides the whole time — which is how this instance survived while its siblings were
|
|
2041
|
+
caught. A future inference value turning that pin red is by design; the note at the pin says
|
|
2042
|
+
so, and names the two correct repairs.
|
|
2043
|
+
- a100f77: The dashboard package now holds ONE relation predicate instead of two that
|
|
2044
|
+
agreed only because a sweep had just aligned them (objectui#5876).
|
|
2045
|
+
|
|
2046
|
+
`computeLookupExpand` in `ObjectDataTable.tsx` carried its own `isLookup`,
|
|
2047
|
+
byte-identical to the exported `isLookupType` in `recordFields.tsx` after
|
|
2048
|
+
objectui#5692 pointed both at `@object-ui/core`'s `EXPANDABLE_FIELD_TYPES`.
|
|
2049
|
+
Nothing kept them aligned: a future edit to either — a member added, the
|
|
2050
|
+
retirement gate moved — would have re-forked the `$expand` decision from the
|
|
2051
|
+
predicate whose docblock claims to drive it. `computeLookupExpand` now calls
|
|
2052
|
+
`isLookupType`, which gains its first production consumer, and the module no
|
|
2053
|
+
longer imports the shared family or the retirement gate at all.
|
|
2054
|
+
|
|
2055
|
+
**No behaviour changes**, and that is measured rather than assumed:
|
|
2056
|
+
|
|
2057
|
+
- The two bodies were identical, so every boolean answer — `tree` is expanded,
|
|
2058
|
+
`reference` is not, ordinary relations are — is the same before and after.
|
|
2059
|
+
- The retired-spelling warning is not emitted a different number of times.
|
|
2060
|
+
`reportRetiredFieldType` dedupes per SPELLING in one module-level set inside
|
|
2061
|
+
`@object-ui/core`, which both bodies already shared, so routing two callers
|
|
2062
|
+
through one function cannot change the count.
|
|
2063
|
+
|
|
2064
|
+
Nothing published moves: `isLookupType` is not re-exported from
|
|
2065
|
+
`@object-ui/plugin-dashboard`'s entry, so this is internal shape only.
|
|
2066
|
+
|
|
2067
|
+
Because a refactor with no observable delta cannot be pinned by a behavioural
|
|
2068
|
+
test — a byte-identical local copy satisfies every assertion you can write
|
|
2069
|
+
about `$expand` — the pin is identity, in
|
|
2070
|
+
`__tests__/expandableFamily.identity-5692.test.ts`: `computeLookupExpand` is
|
|
2071
|
+
observed CALLING `isLookupType`, and `ObjectDataTable.tsx` is read at source
|
|
2072
|
+
level to confirm no second body survives for it to call instead.
|
|
2073
|
+
- 99a3c2d: `BaseSchema` declares `bind`, the data-scope binding path, on both halves — the TypeScript
|
|
2074
|
+
interface and its Zod mirror (objectui#6357).
|
|
2075
|
+
|
|
2076
|
+
`bind` was read by ten production sites and declared by no schema shape. It resolved as `any`
|
|
2077
|
+
through `BaseSchema`'s index signature and rode `.passthrough()` on the validator, while three
|
|
2078
|
+
separate documents taught it as an authorable key of *every* node: this repo's own `AGENTS.md`
|
|
2079
|
+
§4 ("Every node in the UI tree follows this shape (`@object-ui/types`)"), the published
|
|
2080
|
+
agent-facing `skills/objectui/rules/protocol.md` ("Every UI component node MUST follow this
|
|
2081
|
+
shape"), and `content/docs/fields/grid.mdx`. So the agent-facing protocol told authors to write
|
|
2082
|
+
a key the published types did not know existed.
|
|
2083
|
+
|
|
2084
|
+
The census chose the home rather than guessing it. Nine reads go through
|
|
2085
|
+
`useDataScope(schema.bind)` — `list` and `tree-view` in `@object-ui/components`, and the
|
|
2086
|
+
`object-*` widgets in `plugin-charts`, `plugin-dashboard` (×2), `plugin-grid`, `plugin-kanban`,
|
|
2087
|
+
`plugin-list`, `plugin-timeline`. A tenth is `plugin-grid`'s `gridNeedsDataSource` predicate,
|
|
2088
|
+
where a present `bind` is one of the escape hatches that makes a missing data-source adapter
|
|
2089
|
+
legitimate. Two more sites destructure the key out so `SchemaRenderer`'s prop spread cannot
|
|
2090
|
+
write `bind="data.revenue"` onto the DOM. Per-component declaration was measured and rejected:
|
|
2091
|
+
it costs nine copies of one key and buys nothing extra, because neither half can refuse the key
|
|
2092
|
+
on a non-reader either way. `placeholder` is the standing precedent for a cross-cutting key
|
|
2093
|
+
declared on `BaseSchema` and honoured only by a subset.
|
|
2094
|
+
|
|
2095
|
+
**Accept-set narrowing, on the value and not the key.** `bind: 42` type-checked and parsed green
|
|
2096
|
+
before this change and is refused by both halves now. It only refuses what already crashed:
|
|
2097
|
+
`useDataScope` is `(path?: string)` and resolves via `path.split('.')`, so a non-string `bind`
|
|
2098
|
+
threw a `TypeError` at render time. Every `bind` authored in this repo is a string, and the
|
|
2099
|
+
declaration is optional, so nothing that renders today stops.
|
|
2100
|
+
|
|
2101
|
+
**What this does NOT change**, stated because the pin would otherwise be read as more than it is:
|
|
2102
|
+
an *undeclared* key is still accepted by both halves, so this did not buy rejection of a
|
|
2103
|
+
misspelling such as `bindTo` (objectui#5155 / objectui#6269 own that ceiling). And `data-table`
|
|
2104
|
+
still does not call `useDataScope`, so a `bind` on it is still ignored and still renders a header
|
|
2105
|
+
over an empty body with no error — a documented silent failure that this declaration neither
|
|
2106
|
+
causes nor cures, since the key was accepted on every node before it existed.
|
|
2107
|
+
|
|
2108
|
+
`ObjectPivotTable` drops its local `bind?: string`: its `PivotTableSchema & {…}` intersection
|
|
2109
|
+
extends `BaseSchema`, so the member was a true duplicate. Two other local declarations are left
|
|
2110
|
+
in place and ratcheted rather than removed — their containing types never reference `BaseSchema`,
|
|
2111
|
+
so deleting the member would delete the declaration rather than inherit it.
|
|
2112
|
+
- 129c7a9: `ObjectDataTable` no longer writes six undeclared keys into the `data-table` columns slot
|
|
2113
|
+
(objectui#6373). `enrich()` returned `NormalizedColumn`, whose `[key: string]: any` accepts
|
|
2114
|
+
anything, so nothing checked the producer's output against
|
|
2115
|
+
`DataTableSchema.columns: TableColumn[]`: `{ ...col, ...fieldMeta }` spread `label`,
|
|
2116
|
+
`options`, `referenceTo`, `format`, `currency` and `decimals` onto every emitted column, and
|
|
2117
|
+
`TableColumn` declares none of them.
|
|
2118
|
+
|
|
2119
|
+
The measured read set of the consumer (`data-table.tsx`, comments stripped) contains none of
|
|
2120
|
+
the six, so all six retire from the emit rather than being declared — declaring a key nothing
|
|
2121
|
+
reads is the same `declared != enforced` defect facing the other way. Rendering is unchanged
|
|
2122
|
+
because none of those keys was the live path for its own value: the `FieldMeta` the `cell`
|
|
2123
|
+
closure captures is what this widget's type-aware rendering has always read, and it is
|
|
2124
|
+
untouched. Authored spellings still pass through, so a column the author wrote as
|
|
2125
|
+
`{ format: '$0,0' }` keeps its `format` exactly as before.
|
|
2126
|
+
|
|
2127
|
+
`type` is unchanged — the fold `fc62bb490` put at this seam still applies. `name` is unchanged
|
|
2128
|
+
and still written: `data-table` reads `col.accessorKey || col.name` and objectui#5120 holds
|
|
2129
|
+
that alias while two published skill guides still teach a `{ name, label }` column. The hold
|
|
2130
|
+
is now declared at the seam instead of arriving anonymously inside a spread.
|
|
2131
|
+
|
|
2132
|
+
The seam's emit type carries ADR-0049 `?: never` tombstones for the retired keys rather than
|
|
2133
|
+
being a bare `TableColumn` annotation. Measured before the shape was chosen: a bare annotation
|
|
2134
|
+
raises no error at all here, because TypeScript's excess-property check exempts properties
|
|
2135
|
+
that arrive through a spread — it would have type-checked the boundary without enforcing it.
|
|
2136
|
+
- 38a123c: Land objectui#6425's per-key ruling for `ObjectDataTable`'s authored column
|
|
2137
|
+
override keys (maintainer, 2026-08-27):
|
|
2138
|
+
|
|
2139
|
+
- **Declare `format`, `options`, `currency`** on `TableColumn` and its
|
|
2140
|
+
`TableColumnSchema` zod mirror, in the same stroke. All three are honoured
|
|
2141
|
+
by `object-data-table`'s cell pipeline — `format` / `options` were
|
|
2142
|
+
documented author overrides the published types refused (a typed author got
|
|
2143
|
+
a compile error and the zod parse silently stripped the key); `currency`
|
|
2144
|
+
shipped in production but was never promised. The zod mirror now passes the
|
|
2145
|
+
keys through instead of stripping them; `StaticTableColumn` and its mirror
|
|
2146
|
+
tombstone all three under the #5474 lockstep rule (the static renderer
|
|
2147
|
+
reads no field-meta overrides).
|
|
2148
|
+
- **Retire `decimals`**, immediately: zero readers measured anywhere
|
|
2149
|
+
(`NumberCellRenderer` reads `scale`, `PercentCellRenderer` reads
|
|
2150
|
+
`precision`), so no authored `decimals` could reach a render. The authored
|
|
2151
|
+
read is removed and the key falls into `AuthoredColumnOverrides`' derived
|
|
2152
|
+
refusal band — render output is pinned unchanged.
|
|
2153
|
+
- **Re-arm plugin-grid's #6004 `options` retirement with an explicit
|
|
2154
|
+
tombstone**: that refusal rested on the key's NON-membership (excess-property
|
|
2155
|
+
freshness), which declaring `options` on `TableColumn` silently ended.
|
|
2156
|
+
`ObjectGridRetiredOptionsTombstone` (`?: never`, intersected into both
|
|
2157
|
+
`ObjectGridColumnDraft` and `ObjectGridColumn`) restores the refusal by
|
|
2158
|
+
assignability; #6004's verdict itself is unchanged.
|
|
2159
|
+
- **`referenceTo` is deliberately NOT declared as spelled** — it stays held,
|
|
2160
|
+
owned by objectui#6597 (fix the spelling chain or withdraw the README
|
|
2161
|
+
line). The remaining hold is that card's scope, not unfinished work here.
|
|
2162
|
+
- aff3d7a: A `bind` authored on a `data-table` is now diagnosed at render instead of ignored in
|
|
2163
|
+
silence (objectui#6575).
|
|
2164
|
+
|
|
2165
|
+
`bind` is the data-scope vocabulary: a path string resolved by `useDataScope()`.
|
|
2166
|
+
`list`, `tree-view` and the `object-*` plugin widgets read it. `data-table` does not
|
|
2167
|
+
— it takes its rows from an inline `data` array on the node and never calls the hook.
|
|
2168
|
+
A `bind` on a `data-table` was nevertheless accepted by every gate: the TS side via
|
|
2169
|
+
`BaseSchema`'s index signature, the zod side via `BaseSchema` being `.passthrough()`,
|
|
2170
|
+
which `DataTableSchema.extend(…)` inherits. Nothing read it at render, so the author
|
|
2171
|
+
got a table drawing a correct-looking header over the "No results found" empty state,
|
|
2172
|
+
with no error and no warning — a success receipt for a disagreement between the
|
|
2173
|
+
author and the renderer, and the hardest failure shape for a human or an AI author to
|
|
2174
|
+
self-check.
|
|
2175
|
+
|
|
2176
|
+
The platform was already paying for this in teaching rather than in diagnostics:
|
|
2177
|
+
`skills/objectui/rules/protocol.md` documents the pothole verbatim and a pin test
|
|
2178
|
+
locks the behaviour. The warning now also reaches the console, where the author who
|
|
2179
|
+
did not read the docs is standing:
|
|
2180
|
+
|
|
2181
|
+
> `bind: 'customers'` is ignored: data-table does not read `bind`; it reads its rows
|
|
2182
|
+
> from the inline `data` array on the node. This node has no inline rows, so the
|
|
2183
|
+
> table renders its header over an empty body.
|
|
2184
|
+
|
|
2185
|
+
It names the node's address, the path that was spelled, and the way out. The
|
|
2186
|
+
consequence clause is measured rather than asserted: a node carrying BOTH `data` and
|
|
2187
|
+
`bind` is not empty, and is told that its rows came from `data` and its `bind`
|
|
2188
|
+
contributed nothing.
|
|
2189
|
+
|
|
2190
|
+
**No behaviour change.** `data-table` still does not read `bind`, and per the
|
|
2191
|
+
2026-08-27 ruling it must not start — making it a `useDataScope` reader is a separate
|
|
2192
|
+
published-surface question needing its own ruling, including a `data`-vs-`bind`
|
|
2193
|
+
precedence. Refusing the key at parse stays blocked on the `.passthrough()` ceiling
|
|
2194
|
+
(objectui#5155 / objectui#6269). The trap stops being silent; it does not stop being
|
|
2195
|
+
a trap. The channel is the one `plugin-grid`'s `columnSpellingDiagnostics.ts` already
|
|
2196
|
+
uses for this exact shape of failure — a pure describe function, a `useEffect` keyed
|
|
2197
|
+
on the schema slice, one `console.warn`, no NODE_ENV branch.
|
|
2198
|
+
|
|
2199
|
+
`ObjectDataTable` (`@object-ui/plugin-dashboard`) stops forwarding a `bind` it has
|
|
2200
|
+
already consumed. It resolves the binding itself via `useDataScope(schema.bind)` and
|
|
2201
|
+
then delegated with `{ ...schema, type: 'data-table', … }`, which handed the spent
|
|
2202
|
+
key to a component that cannot read one. Without this, a correctly authored and
|
|
2203
|
+
published-guide-taught `object-data-table` would have tripped the new diagnostic on
|
|
2204
|
+
every render, over rows that were on screen precisely because its `bind` had been
|
|
2205
|
+
honoured. The key is stopped where it was spent — the same shape its sibling
|
|
2206
|
+
`DashboardGridLayout` already uses for `data`. Nothing else about that delegation
|
|
2207
|
+
moved, and the bound rows still arrive.
|
|
2208
|
+
- 4ca30d0: Two widget prop types anchor their `schema` to exported schema types that extend
|
|
2209
|
+
`BaseSchema`, instead of hand-rolled inline literals with no `BaseSchema` in
|
|
2210
|
+
their ancestry (objectui#6576, maintainer ruling 2026-08-31 option A; folds
|
|
2211
|
+
objectui#6914).
|
|
2212
|
+
|
|
2213
|
+
- `@object-ui/types` exports `ObjectGallerySchema` (`type: 'object-gallery'`)
|
|
2214
|
+
and `ObjectDataTableSchema` (`type: 'object-data-table'`), each `extends
|
|
2215
|
+
BaseSchema`, beside the other `Object*Schema` declarations, with zod mirrors
|
|
2216
|
+
of the same names under `@object-ui/types/zod`. `ObjectDataTableSchema`
|
|
2217
|
+
declares the two keys the widget was reading behind casts — `drillDown`
|
|
2218
|
+
(`DrillDownConfig`, at this change) and `onRowClick` — which no declaration
|
|
2219
|
+
carried before.
|
|
2220
|
+
|
|
2221
|
+
⚠️ **Dated note, 2026-09-25 — `drillDown` now takes the table's own shape —
|
|
2222
|
+
objectui#10685.** `ObjectDataTableSchema.drillDown` is
|
|
2223
|
+
`ObjectDataTableDrillDownConfig`, on both faces: `filter`, `maxRows` and
|
|
2224
|
+
`report` are refused by name, and `target` is `'drawer'` or `'dialog'`. None of
|
|
2225
|
+
the four was ever read by this block. The rest of this entry is kept as the
|
|
2226
|
+
reading of this change.
|
|
2227
|
+
- `@object-ui/plugin-list`: the published `ObjectGalleryProps.schema` is
|
|
2228
|
+
`ObjectGallerySchema`. Its accept set WIDENS — every `BaseSchema` member is
|
|
2229
|
+
writable (`visibleWhen`, a real base member, was a compile error on the
|
|
2230
|
+
literal) — and NARROWS in one place: `type` is now required and pinned to
|
|
2231
|
+
`'object-gallery'`. `data` stays `Record<string, unknown>[]`.
|
|
2232
|
+
- `@object-ui/plugin-dashboard`: `ObjectDataTableProps.schema` (not exported
|
|
2233
|
+
from the plugin index) is `ObjectDataTableSchema`. The literal's own
|
|
2234
|
+
`[key: string]: any` is gone, so a wrong-typed base member (`visible: 42`) and
|
|
2235
|
+
a wrong-shaped `drillDown` are refused, and `type` is pinned to
|
|
2236
|
+
`'object-data-table'` instead of bare `string`.
|
|
2237
|
+
|
|
2238
|
+
Unchanged on both, stated plainly: an UNKNOWN key still compiles, because
|
|
2239
|
+
`BaseSchema`'s index signature is inherited (objectui#5155, open). No runtime
|
|
2240
|
+
behaviour changes; the widgets render exactly as before.
|
|
2241
|
+
- 3beef6d: The spec's `dataSource` element binding is now DECLARED by the blocks that read
|
|
2242
|
+
it, so the html tier stops reporting the one working saved-view spelling as
|
|
2243
|
+
`unknown-prop` (objectui#6678).
|
|
2244
|
+
|
|
2245
|
+
`PageComponentSchema.dataSource` — `{ object, view, filter, sort, limit }` — is
|
|
2246
|
+
the one spelling that resolves a saved view for an object-bound block. It works,
|
|
2247
|
+
and it drew the identical `unknown-prop` warning as the two spellings that do
|
|
2248
|
+
nothing (`viewName`, `view`), because `validateTree` looks a prop up in the
|
|
2249
|
+
block's declared `inputs` and no registration declared this key. On the tier
|
|
2250
|
+
built to accept AI-authored pages, where the diagnostic IS the contract, the
|
|
2251
|
+
only signal pointed away from the key that works.
|
|
2252
|
+
|
|
2253
|
+
Adopting the maintainer ruling of 2026-08-29 — option B **in the injection
|
|
2254
|
+
form**:
|
|
2255
|
+
|
|
2256
|
+
- `ELEMENT_DATA_SOURCE_INPUT` is the single declaration, in `@object-ui/core`
|
|
2257
|
+
beside the binding's own semantics; `Registry.register` emits it for any
|
|
2258
|
+
registration whose renderer passed through the new `elementDataSourceBlock()`
|
|
2259
|
+
seam. One mechanism, one copy — not a hand-kept declaration per block, which is
|
|
2260
|
+
the shape that drifts and that a new block forgets. The seam lives in
|
|
2261
|
+
`@object-ui/core` and is re-exported by `@object-ui/react` beside
|
|
2262
|
+
`ElementDataSourceGate` for discoverability; call sites take the core import,
|
|
2263
|
+
because a registration runs at module scope and this repo's suites partially
|
|
2264
|
+
mock `@object-ui/react`.
|
|
2265
|
+
- Seventeen renderers, in thirteen files across twelve packages, reach the seam
|
|
2266
|
+
and now publish the key to the save gate, the parser whitelist, the generated
|
|
2267
|
+
JSX authoring types and the block list. The card named nine blocks; the tree
|
|
2268
|
+
also has `plugin-grid`, `plugin-timeline`, two further `plugin-form` blocks and
|
|
2269
|
+
`element:record_picker` — nothing was hand-listed, so the mechanism covered
|
|
2270
|
+
them. `element:record_picker` consumes the gate's HOOK and status panels rather
|
|
2271
|
+
than the wrapper tag (its object lives under `properties`), and was found by a
|
|
2272
|
+
render probe rather than by reading sources.
|
|
2273
|
+
- `dataSource` on a block that does NOT read it (`flex`, `card`) still reports
|
|
2274
|
+
`unknown-prop`. Adding the key to `sdui-parser`'s `BASE_PROPS` was refused for
|
|
2275
|
+
exactly this reason — that set mirrors `BaseSchema`, and silencing the key
|
|
2276
|
+
everywhere would make the diagnostic lie in the other direction.
|
|
2277
|
+
- New `check:element-data-source-declaration` fails any source that consumes the
|
|
2278
|
+
gate without reaching the seam, so a block added tomorrow cannot forget.
|
|
2279
|
+
|
|
2280
|
+
Behaviour of the binding itself is unchanged — this is a declaration, not a
|
|
2281
|
+
resolution change. The saved view still resolves its columns, and an
|
|
2282
|
+
unresolvable `view` still fails loudly rather than widening to the object's full
|
|
2283
|
+
scope.
|
|
2284
|
+
|
|
2285
|
+
The spec/registry parity gates (repo-wide and the `record:related_list` per-block
|
|
2286
|
+
pin) now derive their accepted set from the WHOLE node contract rather than from
|
|
2287
|
+
`ComponentPropsMap[type]` alone. `PageComponentSchema` accepts and keeps
|
|
2288
|
+
`dataSource` on a page-component node — it is a node-level key, a sibling of
|
|
2289
|
+
`type` and `className`, not a per-block prop — so the gates' previous complaint
|
|
2290
|
+
was measurably wrong. Derived from the spec, not exempted, and both still
|
|
2291
|
+
discriminate against an invented key.
|
|
2292
|
+
- d636743: Feed the lookup cells in `ObjectDataTable` and `RecordDetailDrawer` their reference target,
|
|
2293
|
+
so schema-aware display-name resolution and drill-through links engage for the first time
|
|
2294
|
+
(objectui#6694).
|
|
2295
|
+
|
|
2296
|
+
Both widgets build their cell meta with `buildFieldMeta` and render it through
|
|
2297
|
+
`renderFieldValue` → `getCellRenderer` → `LookupCellRenderer` (`@object-ui/fields`). That
|
|
2298
|
+
renderer resolves its target from `field.reference_to || field.reference`, and `FieldMeta`
|
|
2299
|
+
carried neither spelling — nor `display_field`. So the renderer resolved `undefined` and two
|
|
2300
|
+
things failed, independently and both silently:
|
|
2301
|
+
|
|
2302
|
+
- `useRefObjectSchema` never loaded the referenced object's schema, so the ADR-0079 /
|
|
2303
|
+
objectui#2357 resolution never ran and every cell fell back to `pickRecordDisplayName`'s
|
|
2304
|
+
generic `.name` / `.title` heuristic. Quiet, because that heuristic usually still produces
|
|
2305
|
+
a readable name — it diverges only when the referenced object's display field is not
|
|
2306
|
+
literally `name` / `title`, and then it silently shows the wrong one.
|
|
2307
|
+
- `ReferencedRecordLink`'s `objectName` was always `undefined`, so `navigable` was always
|
|
2308
|
+
`false` and no lookup cell in either widget ever rendered a real anchor — no
|
|
2309
|
+
drill-through, no middle-click-new-tab, no copy-link. Quiet, because the cell still
|
|
2310
|
+
rendered its value as plain text.
|
|
2311
|
+
|
|
2312
|
+
This RESTORES intended behaviour rather than adding surface. `ReferencedRecordLink` was
|
|
2313
|
+
placed in the shared cell renderer precisely so every surface would get the affordance once
|
|
2314
|
+
("Both surfaces resolve through `LookupCellRenderer`, so the affordance belongs here,
|
|
2315
|
+
once"), and `plugin-grid`'s `ObjectGrid` has fed it all along via `applyRelationalMeta` at
|
|
2316
|
+
all three of its column-building call sites. These two widgets simply never adopted that
|
|
2317
|
+
copy. Nothing new is authorable: the keys come off the OBJECT SCHEMA field def authors
|
|
2318
|
+
already write, never off a column override — the distinction objectui#6597 measured when it
|
|
2319
|
+
retired `referenceTo`, and the reason this needs no new column hold.
|
|
2320
|
+
|
|
2321
|
+
The copy is made once in `buildFieldMeta`, the seam both widgets funnel through, so the two
|
|
2322
|
+
surfaces cannot drift — which is what that module exists for.
|
|
2323
|
+
|
|
2324
|
+
⚠️ The copy set is three keys where `ObjectGrid`'s `RELATIONAL_META_KEYS` is nine, and the
|
|
2325
|
+
difference is measured per key, not preferred. The grid's cells are EDITABLE, so its extra
|
|
2326
|
+
keys drive the inline picker's query (`LookupField` / `UserField` read `id_field`,
|
|
2327
|
+
`description_field`, `lookup_filters`, `lookupFilters`); these two widgets are read-only and
|
|
2328
|
+
their render path ends at a cell renderer. `packages/fields/src/index.tsx` reads exactly
|
|
2329
|
+
`reference_to`, `reference` and `display_field` off a cell's `field` prop; `titleFormat` is
|
|
2330
|
+
never read off a field meta at all (its readers take it off the object schema, which arrives
|
|
2331
|
+
here through `useRefObjectSchema(reference_to)`), and `reference_to_field` has zero member
|
|
2332
|
+
reads anywhere in the repo. Copying the other six would mint six members written on every
|
|
2333
|
+
call and read by nothing — precisely what objectui#6625 (`decimals`) and objectui#6597
|
|
2334
|
+
(`referenceTo`) retired from this same file.
|
|
2335
|
+
|
|
2336
|
+
No published type widened: `FieldMeta` is internal to the package — it is not re-exported
|
|
2337
|
+
through the barrel (`dist/index.d.ts` names neither it nor `recordFields`) and the `exports`
|
|
2338
|
+
map publishes only `"."`, so Node refuses `@object-ui/plugin-dashboard/recordFields` with
|
|
2339
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
2340
|
+
|
|
2341
|
+
Behaviour note for existing dashboards: a lookup cell whose referenced object declares a
|
|
2342
|
+
`nameField` other than `name` / `title` will now show that declared name instead of the
|
|
2343
|
+
heuristic's pick, and valued lookup cells become links wherever the host publishes
|
|
2344
|
+
`recordHref`.
|
|
2345
|
+
|
|
2346
|
+
⚠️ **Dated note, 2026-09-30 — the cell's reads and the copy set have since moved — objectui#11070.** Later in this same release objectui#11070 (round 4) narrowed `LookupCellRenderer` to `reference` alone, so "reads exactly `reference_to`, `reference` and `display_field`" and `useRefObjectSchema(reference_to)` above no longer describe what ships: the cell reads `reference`, and `buildFieldMeta` no longer copies `reference_to` (its `FieldMeta` member is retired, and `ObjectDataTable` refuses it by a hand-written tombstone). `.changeset/11070-reference-to-round4.md` states what ships; the text above is kept as the reading of this change.
|
|
2347
|
+
|
|
2348
|
+
⚠️ **Dated note, 2026-09-30 — `display_field` has left the copy set too — objectui#11070.** The copy set above includes `display_field`. Later in this same release objectui#11070 (its text-family round) removed it from `buildFieldMeta`'s copy and from `FieldMeta`: `LookupCellRenderer` has resolved the display pointer as `displayField`, then `reference_field`, since objectui#7155, so the copied key reached no reader. The copy set that ships is `reference` and `displayField`, and `ObjectDataTable` refuses `display_field` on an authored column by a hand-written tombstone. `.changeset/11070-text-family-round5.md` states what ships; the text above is kept as the reading of this change.
|
|
2349
|
+
- 9700dd9: A lookup cell in `ObjectGrid` now honours the author's `displayField`
|
|
2350
|
+
(objectui#6875).
|
|
2351
|
+
|
|
2352
|
+
`ObjectGrid` copies a set of relational keys off the object-schema field def
|
|
2353
|
+
onto each column's `fieldMeta`, and that bag is what the lookup cell renderer
|
|
2354
|
+
and the inline picker receive. The set was hand-kept and had become a strict
|
|
2355
|
+
SUBSET of what those two consumers read — `displayField`, `descriptionField`
|
|
2356
|
+
and `lookupColumns` were read on the grid's own path and never copied.
|
|
2357
|
+
|
|
2358
|
+
They are the spellings that matter. `@objectstack/spec` 17.2.0's `FieldSchema`
|
|
2359
|
+
is strict and declares `displayField` / `descriptionField` / `lookupColumns` /
|
|
2360
|
+
`lookupFilters` / `reference`, and none of the snake_case twins the copy set
|
|
2361
|
+
mostly carried — those parse to `unrecognized_keys`, so a spec-compliant
|
|
2362
|
+
producer cannot emit them. Nothing renames anything on the way in either: the
|
|
2363
|
+
adapter's `getObjectSchema` choke point rewrites only the `reference` ⇄
|
|
2364
|
+
`reference_to` pair. So an author who declared `displayField: 'project_code'`
|
|
2365
|
+
got a grid cell showing the referenced record's generic `.name` instead.
|
|
2366
|
+
|
|
2367
|
+
- The copy set is now DERIVED, in `plugin-grid/src/relationalMetaKeys.ts`, from
|
|
2368
|
+
a table that classifies every key the consumers read off this bag. A gate
|
|
2369
|
+
re-extracts that read set from the consumer sources on each run and fails on
|
|
2370
|
+
any unclassified spelling or orphan, so the two cannot drift apart again.
|
|
2371
|
+
- `reference_field` and `lookup_columns` — the other two never-copied keys —
|
|
2372
|
+
stay out on purpose: `FieldSchema` declares neither, so no producer can fill
|
|
2373
|
+
them. The gate proves that against the installed spec rather than asserting it
|
|
2374
|
+
in prose.
|
|
2375
|
+
- `plugin-dashboard`'s `CELL_RELATIONAL_META_KEYS` had the same omission in the
|
|
2376
|
+
same fallback chain and gains `displayField` too.
|
|
2377
|
+
|
|
2378
|
+
⚠️ **Dated note, 2026-09-30 — the choke point's one reference rewrite is now one-way — objectui#11070.** "rewrites only the `reference` ⇄ `reference_to` pair" above held when this change landed. Later in this same release objectui#11070 (round 4) made that rewrite a fold onto `reference` that never stamps `reference_to`. Nothing else is rewritten on the way in, so the reasoning above is unaffected. `.changeset/11070-reference-to-round4.md` states what ships; the text above is kept as the reading of this change.
|
|
2379
|
+
- 5ad86dd: **Breaking for authored metadata:** `TextSchema.value` is RETIRED
|
|
2380
|
+
(maintainer ruling A1 of 2026-09-04; objectui#7016; ADR-0049 enforce-or-remove).
|
|
2381
|
+
A `text` node that authors `value` no longer validates: the parse fails loudly on
|
|
2382
|
+
the `value` path with the explanation in the message, the TS member is a
|
|
2383
|
+
`?: never` tombstone so the same document is refused at compile time, and the
|
|
2384
|
+
renderer no longer reads the key. Write `content`.
|
|
2385
|
+
|
|
2386
|
+
**What was measured, on this branch's base.** `TextSchema` declared two spellings
|
|
2387
|
+
for its one content slot — `content` (read first) and `value` (the fallback limb
|
|
2388
|
+
of `{schema.content || schema.value}` at `renderers/basic/text.tsx:162` and
|
|
2389
|
+
`:167`) — both declared by objectui#6150, whose docblock called the pair "a
|
|
2390
|
+
dialect, not a design" and deferred the choice. The ruling's premise, that
|
|
2391
|
+
`value` is the minority spelling, was measured before any edit over the four
|
|
2392
|
+
roots it named: **776 `content`-only `text` nodes, 25 `value`-only, 0 authoring
|
|
2393
|
+
both** across `examples/` (674 / 13), `apps/` (59 / 0), the `examples/`
|
|
2394
|
+
directories under `packages/` (0 / 1) and `content/docs/**` (43 / 11) — a
|
|
2395
|
+
thirty-to-one majority for `content`, so the retirement went ahead as ruled.
|
|
2396
|
+
(A further 14 `{ value, label, type: "text" }` objects in the filter-builder
|
|
2397
|
+
catalog entries are field descriptors whose `type` is a field type, not `text`
|
|
2398
|
+
nodes, and were excluded by kind.)
|
|
2399
|
+
|
|
2400
|
+
**Who is affected — a `value` authored on a `text` node:**
|
|
2401
|
+
|
|
2402
|
+
```json
|
|
2403
|
+
{ "type": "text",
|
|
2404
|
+
"value": "Hello" } // ← was tolerated (rendered as the fallback)
|
|
2405
|
+
```
|
|
2406
|
+
|
|
2407
|
+
now fails validation with:
|
|
2408
|
+
|
|
2409
|
+
> RETIRED (ADR-0049) — `value` is no longer part of TextSchema; write
|
|
2410
|
+
> `content`. It was a second spelling of the one content slot, read only as the
|
|
2411
|
+
> fallback limb of `schema.content || schema.value`, and was retired under
|
|
2412
|
+
> ADR-0049 enforce-or-remove with no deprecation window (maintainer ruling A1,
|
|
2413
|
+
> 2026-09-04). The renderer reads `content` alone now, so an authored `value`
|
|
2414
|
+
> would render nothing. Rename the key; the string is unchanged.
|
|
2415
|
+
|
|
2416
|
+
**Two published faces, one retirement.** The TypeScript interface `TextSchema`
|
|
2417
|
+
(`@object-ui/types`, `layout.ts`) declares `value?: never`; the Zod mirror
|
|
2418
|
+
`TextSchema` (`@object-ui/types/zod`, `layout.zod.ts`) declares `value` as a
|
|
2419
|
+
`retirementTombstone()`, so the key stays DECLARED and is refused BY NAME —
|
|
2420
|
+
a plain deletion would have let an authored `value` ride `BaseSchema`'s
|
|
2421
|
+
`.passthrough()` into a silent blank, which is worse than the tolerated
|
|
2422
|
+
fallback it replaces. The `value?: string` members of `TextSpanSchema` and
|
|
2423
|
+
`TabsSchema` in the same file are other schemas' contracts and are unchanged.
|
|
2424
|
+
|
|
2425
|
+
**`@object-ui/components`** — the `text` renderer renders `{schema.content}` at
|
|
2426
|
+
both arms (the `|| schema.value` limb is gone from each), and the `context-menu`
|
|
2427
|
+
renderer's built-in fallback trigger node now spells `content`. Nothing else in
|
|
2428
|
+
the package moves. **`@object-ui/plugin-dashboard`** — its three placeholder
|
|
2429
|
+
`text` nodes ("chart type is not supported yet", "Custom widget — set
|
|
2430
|
+
`component`…", the retired-widget notice) spell `content` so they keep rendering;
|
|
2431
|
+
their wording is unchanged and still pinned.
|
|
2432
|
+
|
|
2433
|
+
**Who is NOT affected.** A document that already wrote `content` is untouched;
|
|
2434
|
+
`content`, `variant`, `align` and `className` are unchanged; `absent` stays
|
|
2435
|
+
valid (`{ "type": "text" }` still parses). Every in-repo document that authored
|
|
2436
|
+
`value` on a `text` node was rewritten to `content` in the same change: nine
|
|
2437
|
+
`examples/schema-catalog` entries, `packages/types/examples/zod-validation-example.ts`,
|
|
2438
|
+
eleven doc fences under `content/docs/`, and the `@object-ui/components`,
|
|
2439
|
+
`@object-ui/react` and `@object-ui/types/zod` README samples; the catalog is now
|
|
2440
|
+
pinned tree-wide against the retired spelling.
|
|
2441
|
+
|
|
2442
|
+
**Migration:** rename `value` to `content` on every `text` node; the string is
|
|
2443
|
+
unchanged. If a document authored both, `content` was already the value that
|
|
2444
|
+
rendered — delete `value`.
|
|
2445
|
+
|
|
2446
|
+
Graded `minor`, not `patch`: this narrows the accepted input set, which is
|
|
2447
|
+
breaking for any author who wrote the tolerated spelling. It is not `major` per
|
|
2448
|
+
this repo's fixed-group convention (objectui's own breaking changes ship as
|
|
2449
|
+
`minor`; the group's major tracks `@objectstack` — AGENTS.md 版本号策略,
|
|
2450
|
+
mechanically enforced by `scripts/check-changeset-no-major.mjs`).
|
|
2451
|
+
- 6411def: FLS-gate the `$expand` projection at the five remaining build sites (objectui#7230).
|
|
2452
|
+
|
|
2453
|
+
objectui#7215 / PR #7229 gated `$expand` at the two projection sites in its scope
|
|
2454
|
+
(`ObjectGrid`, `ListView`). The helper is reached from more places than that. This
|
|
2455
|
+
closes the five that were left: `ObjectCalendar`, `ObjectGantt`, `RecordDetailView`,
|
|
2456
|
+
`DetailView`, and `ObjectDataTable` (which builds its own whitelist in
|
|
2457
|
+
`computeLookupExpand` rather than calling `buildExpandFields`).
|
|
2458
|
+
|
|
2459
|
+
**Three of them pass no column list at all**, which makes them the sharp ones:
|
|
2460
|
+
`buildExpandFields` reads an absent column list as "no column restriction" and falls
|
|
2461
|
+
back to **every declared relation on the object**, denied ones included. So a standalone
|
|
2462
|
+
calendar, a gantt, and every record page in the console asked the server to resolve the
|
|
2463
|
+
object's full relation set by default rather than by configuration.
|
|
2464
|
+
|
|
2465
|
+
**`DetailView` was input-gated, and that is the defect rather than the fix.** Its column
|
|
2466
|
+
list is already FLS-filtered field by field, which is exactly the route PR #7229 measured
|
|
2467
|
+
as unsound: an emptied column list reads as "no column restriction", so a detail view
|
|
2468
|
+
whose authored fields are all denied had its `$expand` **widened** from the relations it
|
|
2469
|
+
asked for to every relation the object declares. The principal who may read least was
|
|
2470
|
+
asking for the most.
|
|
2471
|
+
|
|
2472
|
+
**Reproduced before it was fixed**, as a failing test per site.
|
|
2473
|
+
|
|
2474
|
+
**Grading, measured rather than assumed.** Against ObjectStack's own server this is
|
|
2475
|
+
defence-in-depth, exactly as objectui#6898 and #7215 are: `plugin-security`'s
|
|
2476
|
+
`FieldMasker.maskRecord` deletes every unreadable key from each returned row and
|
|
2477
|
+
objectql's expand path writes the resolved record back under that same key, so one
|
|
2478
|
+
statement removes the expanded object and the bare id alike; the expansion sub-read is
|
|
2479
|
+
itself gated (`__expandRead` takes the referenced object's full CRUD + RLS + FLS
|
|
2480
|
+
treatment). It is load-bearing for any backend that does not strip, and the
|
|
2481
|
+
client-request side is real regardless.
|
|
2482
|
+
|
|
2483
|
+
**Nothing a permitted view did stops working.** The gate judges each helper's OUTPUT,
|
|
2484
|
+
which contains only the object's declared reference-bearing fields, so the "`checkField`
|
|
2485
|
+
answers false for an undeclared key" trap cannot be reached and derived / host-joined
|
|
2486
|
+
columns are untouched. An unanswered permission policy filters nothing. Neither
|
|
2487
|
+
`buildExpandFields` nor `computeLookupExpand` is changed.
|
|
2488
|
+
- 47547d0: Localize the server's built-in aggregate measure titles on dataset charts
|
|
2489
|
+
(objectui#7258 — consumer half of the objectstack#14492 contract; maintainer
|
|
2490
|
+
ruling B, 2026-09-02).
|
|
2491
|
+
|
|
2492
|
+
A dataset-bound chart's aggregate axis / legend title read the analytics
|
|
2493
|
+
service's hard-coded English `Count` on a zh console whose category labels were
|
|
2494
|
+
already Chinese. The renderer was passing `fields[].label` through verbatim —
|
|
2495
|
+
correctly, for an author-declared measure (objectui#4106) — and had no way to
|
|
2496
|
+
tell the server's built-in default apart from an author's label.
|
|
2497
|
+
|
|
2498
|
+
The wire now can: `AnalyticsResult.fields[]` gains an OPTIONAL structural
|
|
2499
|
+
discriminator, `builtinAggregate?: 'count' | 'sum' | 'avg' | 'min' | 'max' |
|
|
2500
|
+
'count_distinct'`, populated only on the server-side built-in defaults
|
|
2501
|
+
(objectstack#14492). This change is the consumer side of that contract:
|
|
2502
|
+
|
|
2503
|
+
- `@object-ui/core`: `buildChartSeries` now accepts `ChartMeasureField[]` —
|
|
2504
|
+
`ChartResultField` plus the optional `builtinAggregate` carrier
|
|
2505
|
+
(`BuiltinAggregateCarrier`), declared beside the renderer shape rather than
|
|
2506
|
+
on it because the spec this release is built against does not carry the key
|
|
2507
|
+
yet; new `BUILTIN_AGGREGATES` / `BuiltinAggregate` / `isBuiltinAggregate` /
|
|
2508
|
+
`resolveMeasureLabel`; `ChartSeriesOptions.builtinAggregateLabels` carries
|
|
2509
|
+
the locale strings in (core stays React-free and i18n-free — the same
|
|
2510
|
+
division as `nullCategoryLabel`). A field carrying a recognised
|
|
2511
|
+
discriminator resolves through that map; every other field keeps its wire
|
|
2512
|
+
`label` verbatim — never by matching the label's text or the field's name
|
|
2513
|
+
(the rejected option A).
|
|
2514
|
+
- `@object-ui/i18n`: `builtinAggregateLabels(tt)` resolves the six strings
|
|
2515
|
+
through the existing `report.aggregate.*` keys (zh already carried 计数 /
|
|
2516
|
+
求和 / 平均 / …; all ten packs are pinned to cover the vocabulary).
|
|
2517
|
+
- `plugin-charts` (`ObjectChart`), `plugin-dashboard` (`DatasetWidget`),
|
|
2518
|
+
`plugin-report` (`DatasetReportRenderer`): pass the resolved map to
|
|
2519
|
+
`buildChartSeries`.
|
|
2520
|
+
|
|
2521
|
+
Before: 合作中 / 已流失 / 潜在 under an axis titled `Count`. After: the same
|
|
2522
|
+
chart titled `计数`; an `en` session still reads `Count`; an author-labelled
|
|
2523
|
+
measure (`Tasks`) and a measure literally named `count` without the
|
|
2524
|
+
discriminator are byte-for-byte unchanged. Until the upstream field is
|
|
2525
|
+
populated the wire carries no discriminator and every chart renders exactly as
|
|
2526
|
+
before.
|
|
2527
|
+
- faf5269: A dataset-bound KPI tile now renders the sub-caption its author declared in
|
|
2528
|
+
`options.description` (objectui#7293).
|
|
2529
|
+
|
|
2530
|
+
The sub-caption slot was wired end to end and consumed by nothing. It has its own
|
|
2531
|
+
translation key (`{ns}.dashboards.{dash}.widgets.{id}.subCaption`), the server's
|
|
2532
|
+
`translateDashboard` overlays that translation onto `options.description`, and
|
|
2533
|
+
`DashboardRenderer`'s `tWidgetSubCaption` resolves it — but only onto the two
|
|
2534
|
+
inline arms of `getComponentSchema()`. `dataset` is REQUIRED on
|
|
2535
|
+
`DashboardWidgetSchema` (published `@objectstack/spec@17.4.0`: the required keys
|
|
2536
|
+
are exactly `id` / `dataset` / `values`), so every spec-legal widget renders
|
|
2537
|
+
through `DatasetWidget` instead, which read the key nowhere. Every author who
|
|
2538
|
+
wrote a sub-caption got silence.
|
|
2539
|
+
|
|
2540
|
+
`DatasetWidget`'s metric branch now reads the key and renders it in the caption
|
|
2541
|
+
row, resolving it through the same `pickLocalized` seam every other authored
|
|
2542
|
+
label channel uses, so an inline per-locale map resolves instead of being
|
|
2543
|
+
dropped. A tile that declares no sub-caption renders byte-identical markup.
|
|
2544
|
+
|
|
2545
|
+
Read in `DatasetWidget` rather than passed down as a prop on purpose: both
|
|
2546
|
+
dashboard surfaces (`DashboardRenderer` and `DashboardGridLayout`) route a
|
|
2547
|
+
dataset-bound widget to that one component, so a prop from one dispatch site
|
|
2548
|
+
would have fixed one surface and left the other unchanged.
|
|
2549
|
+
|
|
2550
|
+
`@object-ui/sdui-parser` carries no behaviour change — its `unconsumed-widget-option`
|
|
2551
|
+
census recorded `description` as accepted *despite* having no read site on the
|
|
2552
|
+
dataset-bound path, and that note is now stale prose.
|
|
2553
|
+
- c6198c2: **Breaking for authored metadata:** `ComponentInput.label`, `ComponentInput.defaultValue` and
|
|
2554
|
+
`ComponentInput.advanced` are RETIRED on both faces (objectui#7493 item ① and objectui#7781;
|
|
2555
|
+
maintainer ruling A of 2026-09-06, immediate, no deprecation window; ADR-0049 enforce-or-remove).
|
|
2556
|
+
They are the three keys the manifest serializer does not forward, and nothing read them on any
|
|
2557
|
+
publication or consumption path.
|
|
2558
|
+
|
|
2559
|
+
No manifest ever published them, so no consumer could ever have read them. `sdui-parser`'s
|
|
2560
|
+
serializer (`packages/sdui-parser/src/index.ts`) forwards exactly seven keys per input — `name`,
|
|
2561
|
+
`type`, `of`, `required`, `enum`, `binding`, `description` — so a value authored under any of the three
|
|
2562
|
+
never reached `sdui.manifest.json`, the generated JSX `.d.ts`, or a diagnostic; its boundary type
|
|
2563
|
+
has no slot for them; the registry's data-source seam reads `name` only; and neither the designer
|
|
2564
|
+
nor the app-shell inspectors consult registry `inputs` at all. A structural census over every
|
|
2565
|
+
`inputs:` array in the repository (re-measured on this change's merge-base, `name` 951 and `type`
|
|
2566
|
+
951 as the controls) counted the writes: `label` 908, `defaultValue` 245, `advanced` 9 — written on
|
|
2567
|
+
nearly every registration, read by nothing.
|
|
2568
|
+
|
|
2569
|
+
FROM → TO, per key — all three **TOMBSTONED, not removed**, because the route was measured on
|
|
2570
|
+
the built face before it was chosen: `ComponentInputSchema` is a non-strict `z.object`, and an
|
|
2571
|
+
undeclared key parses GREEN and is silently STRIPPED, so a deletion would have swallowed 1,162
|
|
2572
|
+
authored values in silence. The tombstone is what makes the refusal loud and by name.
|
|
2573
|
+
|
|
2574
|
+
- `label?: string` → `label?: never` on the interface, `retirementTombstone()` on the Zod mirror.
|
|
2575
|
+
Migration: delete the key. An input is identified by its `name` on every path that reaches it;
|
|
2576
|
+
nothing ever rendered a label for it.
|
|
2577
|
+
- `defaultValue?: any` → `defaultValue?: never` / `retirementTombstone()`. Migration: delete the
|
|
2578
|
+
key. The renderer's own fallback read IS the default; tell the author about it in `description`,
|
|
2579
|
+
which IS published. (Tightening the type to `unknown` was ruled out: it closes no error class,
|
|
2580
|
+
since nothing reads the value.)
|
|
2581
|
+
- `advanced?: boolean` → `advanced?: never` / `retirementTombstone()`. Migration: delete the key.
|
|
2582
|
+
No designer surface ever hid an "advanced" input; there is nothing to write instead.
|
|
2583
|
+
|
|
2584
|
+
The retirement kit: `?: never` on `ComponentInput` (`packages/types/src/base.ts`), so authoring one
|
|
2585
|
+
is a `tsc` error at the registration site; `retirementTombstone()` on `ComponentInputSchema`
|
|
2586
|
+
(`packages/types/src/zod/base.zod.ts`), so an authored value is REFUSED at parse time with
|
|
2587
|
+
`code: 'invalid_type'`, the key named in the issue `path`, and the migration note as the message
|
|
2588
|
+
(one string, both channels). Pinned in
|
|
2589
|
+
`packages/types/src/__tests__/component-input-retired-keys-7493.test.ts`, which also holds a
|
|
2590
|
+
tree-scoped absence census over every `inputs:` array under `packages/**` and `apps/**`.
|
|
2591
|
+
|
|
2592
|
+
Accept-set change, stated plainly for reviewers: a document that sets any of the three keys on a
|
|
2593
|
+
`ComponentInput` used to parse GREEN (the value was then dropped by the serializer) and now parses
|
|
2594
|
+
RED. Every in-repo authoring site — 1,199 keys across 110 registration files, the three standalone
|
|
2595
|
+
`ComponentInput[]` arrays and the two named input arrays `tsc` found included — is deleted in the same change, as the ruling's split rule
|
|
2596
|
+
requires; the `WidgetRegistry` seam no longer copies the widget-manifest values onto the synthesized
|
|
2597
|
+
`ComponentInput` (they fed nothing), and the data-source declaration `ELEMENT_DATA_SOURCE_INPUT`
|
|
2598
|
+
drops its `label`. The patch entries on the other packages record exactly that: their registrations
|
|
2599
|
+
stop authoring inert keys, with no runtime or published-manifest change.
|
|
2600
|
+
|
|
2601
|
+
The nine test files that read `defaultValue` off a registration were re-pinned against the
|
|
2602
|
+
renderer's ACTUAL default (its own fallback read, or the `defaultProps` it ships) instead of the
|
|
2603
|
+
declaration that went away; two assertions that only restated the shadow default were dropped with
|
|
2604
|
+
the reason on the line.
|
|
2605
|
+
|
|
2606
|
+
The in-repo zero is what was measured. Whether anything OUTSIDE this repository writes these keys
|
|
2607
|
+
is not measurable from here (the objectui#5674 limit); converting such a write from a silent drop
|
|
2608
|
+
into a named refusal is exactly what the tombstones buy. `WidgetInput`'s own `label` /
|
|
2609
|
+
`defaultValue` / `advanced` (the widget-manifest face) stay declared and writable — nothing has
|
|
2610
|
+
ruled on that face; that it now has no reader either is recorded as objectui#7911.
|
|
2611
|
+
- 1422a92: `DashboardComponentSchema.header` and `.globalFilters` now state
|
|
2612
|
+
`@objectstack/spec`'s `DashboardSchema` member on both faces, the TypeScript
|
|
2613
|
+
interface and the Zod validator (objectui#7759 group A). Both keys are declared
|
|
2614
|
+
by the spec, and each face used to refuse something the other accepted.
|
|
2615
|
+
|
|
2616
|
+
**`header` (TypeScript interface).** The hand-written restatement is gone. The
|
|
2617
|
+
interface inherits the spec's `DashboardHeader` through the same
|
|
2618
|
+
`Omit` projection of the spec `Dashboard` type as its other spec-owned keys. Breaking for code
|
|
2619
|
+
that builds header actions in TypeScript:
|
|
2620
|
+
|
|
2621
|
+
- `actions[].label` is `I18nLabel`: a string or an inline per-locale map. It used
|
|
2622
|
+
to be `string`.
|
|
2623
|
+
- `actions[].actionUrl` is required. It used to be optional.
|
|
2624
|
+
- `actions[].actionType` is the spec's `ActionType` (`url`, `script`, `modal`,
|
|
2625
|
+
`flow`, `api` or `form`). It used to be any string.
|
|
2626
|
+
|
|
2627
|
+
The validator is unchanged here: it always took the spec's `header`.
|
|
2628
|
+
|
|
2629
|
+
**`globalFilters` (Zod validator).** `GlobalFilterSchema` is now the spec's
|
|
2630
|
+
`GlobalFilterSchema` by reference, and its local `options` / `optionsFrom`
|
|
2631
|
+
restatement is gone. Breaking for documents that relied on the looser validator:
|
|
2632
|
+
|
|
2633
|
+
- the bare-string option shorthand (`options: ['EMEA']`) is refused with
|
|
2634
|
+
`invalid_type` at `globalFilters.N.options.M`. Write
|
|
2635
|
+
`{ "value": "EMEA", "label": "EMEA" }`;
|
|
2636
|
+
- an option with no `label` is refused at `globalFilters.N.options.M.label`;
|
|
2637
|
+
- an `optionsFrom` with no `labelField` is refused with `invalid_type` at
|
|
2638
|
+
`globalFilters.N.optionsFrom.labelField`. Name the value field again if the
|
|
2639
|
+
options have no separate label column;
|
|
2640
|
+
- an `optionsFrom.filter` must be a filter-condition object. An array is
|
|
2641
|
+
refused;
|
|
2642
|
+
- an unknown key in a filter is refused with `unrecognized_keys` at
|
|
2643
|
+
`globalFilters.N`. It used to be removed without a message.
|
|
2644
|
+
|
|
2645
|
+
Widening: an option `label` may now be an inline per-locale map, which the spec
|
|
2646
|
+
has always allowed and the filter bar already resolves. The TypeScript type is
|
|
2647
|
+
unchanged, because it already bound the spec's `GlobalFilter`. The runtime is
|
|
2648
|
+
unchanged too: `@object-ui/core` still converts a STORED bare-string option into
|
|
2649
|
+
a pair when it reads the document, and logs a deprecation warning. That
|
|
2650
|
+
conversion keeps its own retirement schedule (objectstack#7917).
|
|
2651
|
+
|
|
2652
|
+
**`@object-ui/plugin-dashboard`.** Before this change, a header action whose
|
|
2653
|
+
`label` was an inline per-locale map crashed the renderer: React reported
|
|
2654
|
+
`Objects are not valid as a React child`. The mirror accepted that document.
|
|
2655
|
+
`DashboardRenderer` now shows the label in the active language, both as the
|
|
2656
|
+
button text and as the fallback for the
|
|
2657
|
+
`dashboards.NAME.actions.KEY.label` bundle lookup.
|
|
2658
|
+
|
|
2659
|
+
⚠️ **Dated note, 2026-09-29 — the runtime bare-string lift has since retired —
|
|
2660
|
+
objectui#4356.** Later in this same release `@object-ui/core`'s
|
|
2661
|
+
`normalizeFilterOptions` stopped converting a STORED bare-string option into a
|
|
2662
|
+
pair: a `globalFilters[].options` member that is not a `{ value, label }` object
|
|
2663
|
+
now yields no option, a shorthand-only filter resolves with no `options`, and a
|
|
2664
|
+
mixed array keeps only its object members; in development a once-per-filter
|
|
2665
|
+
`console.warn` names the filter and the dropped members. The objectstack#7917
|
|
2666
|
+
retirement window is closed (maintainer, 2026-09-02, verbatim 「objectstack#7917
|
|
2667
|
+
不考虑现有数据」). So "The runtime is unchanged too: `@object-ui/core` still
|
|
2668
|
+
converts a STORED bare-string option into a pair when it reads the document, and
|
|
2669
|
+
logs a deprecation warning" and "That conversion keeps its own retirement
|
|
2670
|
+
schedule (objectstack#7917)" above no longer hold; the validator's refusal of
|
|
2671
|
+
`options: ['EMEA']` described above is unchanged and now matches the read path.
|
|
2672
|
+
`.changeset/retire-options-shorthand-lift-4356.md` (PR objectui#10930) states
|
|
2673
|
+
what ships; the text above is kept as the reading of this change.
|
|
2674
|
+
- fc32921: Fix a dashboard chart widget with a FIELDLESS `count` aggregate plotting nothing
|
|
2675
|
+
(objectui#8266).
|
|
2676
|
+
|
|
2677
|
+
A widget bound to an object with `aggregate: { function: 'count', groupBy: 'status' }`
|
|
2678
|
+
and no `field` — the normal way to author "how many records per status" — rendered an
|
|
2679
|
+
empty chart. No error, no empty state: a plot frame with the category ticks drawn and
|
|
2680
|
+
not one mark in it, which reads exactly like "this object has no rows yet".
|
|
2681
|
+
|
|
2682
|
+
**Cause.** The two dashboard relays (`DashboardGridLayout`, `DashboardRenderer`) each
|
|
2683
|
+
built the series binding as `aggregate?.field || (options.yField || 'value')`, which for
|
|
2684
|
+
a fieldless count resolves to `'value'`. The rows an object-bound fieldless count
|
|
2685
|
+
returns are keyed `'count'` — the alias the engine projects `COUNT(*)` under, pinned
|
|
2686
|
+
since framework#3701. A `dataKey` naming a column no row carries plots nothing, and
|
|
2687
|
+
neither of the renderer's two guards fires on it: the rows DO carry the category key,
|
|
2688
|
+
and the series array is not empty.
|
|
2689
|
+
|
|
2690
|
+
**Fix.** `chartMeasureKey` is a new `@object-ui/core` export delegating to
|
|
2691
|
+
`chartAggregateValueKey` in `@objectstack/spec/ui` — the contract's own derivation of
|
|
2692
|
+
"the value column an object-bound aggregate produces". Both relays now consult it, and
|
|
2693
|
+
the row-projection side (`aggregateValueKey` in `@object-ui/plugin-charts`) is routed
|
|
2694
|
+
through the same function, so the two halves of the question cannot drift again.
|
|
2695
|
+
|
|
2696
|
+
**What moves on screen.** A chart that was blank now draws. Charts that already drew are
|
|
2697
|
+
unaffected: a field-bearing aggregate resolves to its raw field under both the old and
|
|
2698
|
+
the new reading, and a chart with no `aggregate` at all keeps the author's `yField`.
|
|
2699
|
+
One authored key changes meaning: a `yField` written on an object-bound chart that
|
|
2700
|
+
ALSO declares an aggregate no longer wins over the aggregate's own column — it named a
|
|
2701
|
+
record column that a grouped aggregate never returns, so it plotted nothing before.
|
|
2702
|
+
|
|
2703
|
+
**Not fixed here, and out of scope.** The same widget with no `options.xField` is
|
|
2704
|
+
refused by the category-axis guard naming `name`, a key the author never wrote (they
|
|
2705
|
+
wrote `aggregate.groupBy`). That is the category half of the same relay gap and is
|
|
2706
|
+
filed separately.
|
|
2707
|
+
- 8f9d87a: Fix a dashboard chart widget that declares its category as `aggregate.groupBy` being
|
|
2708
|
+
refused for lacking a `name` column (objectui#8269).
|
|
2709
|
+
|
|
2710
|
+
A widget bound to an object with `aggregate: { function: 'count', groupBy: 'status' }`
|
|
2711
|
+
and no `options.xField` rendered a refusal instead of a chart:
|
|
2712
|
+
|
|
2713
|
+
> This chart cannot plot its category axis: no row has a `name` field.
|
|
2714
|
+
|
|
2715
|
+
The author wrote `groupBy: 'status'`. Nothing on screen said `groupBy` was the key that
|
|
2716
|
+
had been ignored, and `name` appeared nowhere in their metadata — so the diagnostic sent
|
|
2717
|
+
them to debug the wrong layer.
|
|
2718
|
+
|
|
2719
|
+
**Cause.** The two dashboard relays (`DashboardGridLayout`, `DashboardRenderer`) each
|
|
2720
|
+
floored the category binding on a literal — `options.xField || 'name'` — and handed it to
|
|
2721
|
+
the `object-chart` node without ever consulting the aggregate that decides it. An
|
|
2722
|
+
object-bound aggregate returns one row per group keyed by the raw `groupBy` field, so no
|
|
2723
|
+
row carried `name` and the category-axis guard (framework#4033) fired correctly on a
|
|
2724
|
+
binding that was already wrong when it arrived.
|
|
2725
|
+
|
|
2726
|
+
**Fix.** `chartCategoryKey` is a new `@object-ui/core` export delegating to
|
|
2727
|
+
`chartAggregateCategoryKey` in `@objectstack/spec/ui` — the contract's own derivation of
|
|
2728
|
+
"the category column an object-bound aggregate produces", and the published sibling of the
|
|
2729
|
+
`chartAggregateValueKey` that objectui#8266 adopted for the measure axis. Both relays now
|
|
2730
|
+
consult it for the object-provider branch.
|
|
2731
|
+
|
|
2732
|
+
**What moves on screen.** A widget that rendered a refusal now draws. Measured through
|
|
2733
|
+
`ChartRenderer` at 480x320 over the rows a fieldless count returns
|
|
2734
|
+
(`[{status:'open',count:2},{status:'paid',count:5}]`): the composed binding went from
|
|
2735
|
+
`xAxisKey: 'name'` — a `missing-category-key` refusal, 0 marks — to `xAxisKey: 'status'`,
|
|
2736
|
+
1 series and 2 marks with the category ticks drawn.
|
|
2737
|
+
|
|
2738
|
+
**Unaffected.** A chart with no `aggregate` at all keeps the author's `xField` (its rows
|
|
2739
|
+
are raw records, so that key is the right one), an UNGROUPED aggregate keeps it too (it
|
|
2740
|
+
returns a single row with no category column), and the authored-literal-rows branch — the
|
|
2741
|
+
`chart` node composed after the object-provider check fails — keeps its floor unchanged.
|
|
2742
|
+
One authored key changes meaning, exactly as objectui#8266's `yField` did: an `xField`
|
|
2743
|
+
written on an object-bound chart that ALSO declares a `groupBy` no longer wins over the
|
|
2744
|
+
aggregate's own column — it named a record column a grouped aggregate never returns, so it
|
|
2745
|
+
produced the same refusal before.
|
|
2746
|
+
- f4abff4: `@object-ui/layout` and `@object-ui/plugin-dashboard` no longer install a React of their
|
|
2747
|
+
own. `react` moves from `dependencies` to `devDependencies` at the same pin (`19.2.8`);
|
|
2748
|
+
`peerDependencies` is untouched, so both packages still ask their host for
|
|
2749
|
+
`^18.0.0 || ^19.0.0` (objectui#8303).
|
|
2750
|
+
|
|
2751
|
+
Both manifests were making two contradictory statements at once: asking the host for React
|
|
2752
|
+
as a peer, and pinning an exact React 19 of their own. The peer range is the one a consumer
|
|
2753
|
+
reads; the pin is the one their installer acts on. A consumer on React 18 therefore
|
|
2754
|
+
satisfied the peer range and **still** got a second, pinned React 19 pulled into their
|
|
2755
|
+
graph on these two packages' account. Two React copies in one tree is the classic cause of
|
|
2756
|
+
`Invalid hook call`, and the failure is not at install time — it is at first render, in the
|
|
2757
|
+
consumer's own component, with nothing pointing back at these manifests.
|
|
2758
|
+
|
|
2759
|
+
Re-measured on `origin/main` at `348725a7c` across all four `pnpm-workspace.yaml` globs
|
|
2760
|
+
(46 packages), rather than inherited from the card:
|
|
2761
|
+
|
|
2762
|
+
declares a react peer AND pins react in dependencies 2 layout, plugin-dashboard
|
|
2763
|
+
declares a react-dom peer AND pins it in dependencies 0 (objectui#8198 took that half)
|
|
2764
|
+
pins react in dependencies with NO react peer 5 runner, site, 3 examples
|
|
2765
|
+
|
|
2766
|
+
The last row is the control that keeps the fix narrow. Those five are applications that
|
|
2767
|
+
supply React rather than libraries that ask for it: one statement, not two, and correctly
|
|
2768
|
+
untouched. Against the 20+ sibling libraries the two packages fixed here were the only
|
|
2769
|
+
outliers, so this converges on the existing house shape — `app-shell`, `auth`,
|
|
2770
|
+
`collaboration`, `i18n`, `mobile` and `permissions` already carry `react` in
|
|
2771
|
+
`devDependencies` at exactly this pin.
|
|
2772
|
+
|
|
2773
|
+
`scripts/__tests__/react-peer-range-norm-3741.test.ts` gains the invariant so a regression
|
|
2774
|
+
goes red rather than being re-found by a consumer. That file already owns React manifest
|
|
2775
|
+
norms across the fixed version group, already walks the workspace globs, and already
|
|
2776
|
+
governs `react` + `react-dom`, so this is a second assertion on an existing guard rather
|
|
2777
|
+
than a new one. It asserts the CONTRADICTION, not "React in `dependencies`" — widening it
|
|
2778
|
+
further would go red on the five applications above for doing the right thing.
|
|
2779
|
+
|
|
2780
|
+
Two gates are green on this shape by construction and neither green was a reading of it.
|
|
2781
|
+
`check:unused-deps` asks whether a declared runtime dependency has a consumer, and `react`
|
|
2782
|
+
is imported by both packages — which is exactly why objectui#8198 could see and remove the
|
|
2783
|
+
`react-dom` half here and could not see this one. `check-changeset-presence.mjs` reads an
|
|
2784
|
+
eight-field allowlist that includes `peerDependencies` and deliberately excludes
|
|
2785
|
+
`dependencies`; its own docblock states the trade ("a runtime dependency bump can be just
|
|
2786
|
+
as user-visible as any of the eight, and this gate still does not see it"). This changeset
|
|
2787
|
+
is therefore owed by the house rule, not demanded by the gate.
|
|
2788
|
+
- 1f5a644: `DashboardGridLayout` and `DashboardRenderer` now share one auto-refresh timer
|
|
2789
|
+
(objectui#8820).
|
|
2790
|
+
|
|
2791
|
+
Each surface used to carry its own copy of the auto-refresh logic: the
|
|
2792
|
+
refreshing indicator, the handler behind the "Refresh All" button, and the
|
|
2793
|
+
interval that reads `refreshIntervalSeconds`. The two copies were identical in
|
|
2794
|
+
logic. They are now one internal hook, `useDashboardAutoRefresh`, that both
|
|
2795
|
+
surfaces call, so the authored period is read in one place.
|
|
2796
|
+
|
|
2797
|
+
Behaviour is unchanged. The timer runs only when the host passes `onRefresh`
|
|
2798
|
+
and `refreshIntervalSeconds` is greater than zero; it is cleared when the
|
|
2799
|
+
dashboard unmounts and re-armed when the period changes.
|
|
2800
|
+
|
|
2801
|
+
A new test mounts both surfaces and counts `onRefresh` calls under fake timers,
|
|
2802
|
+
so a timer that stops firing on either surface fails a test.
|
|
2803
|
+
- 5b6deed: A client i18n bundle sub-caption now reaches a DATASET-BOUND KPI tile
|
|
2804
|
+
(objectui#8889).
|
|
2805
|
+
|
|
2806
|
+
`{ns}.dashboards.{dash}.widgets.{id}.subCaption` had two consumers and only one
|
|
2807
|
+
of them worked on a dataset-bound widget. objectui#7293 taught `DatasetWidget`
|
|
2808
|
+
to render the AUTHORED value (`widget.options.description`), which also carried
|
|
2809
|
+
the server path — `translateDashboard` overlays the resolved translation onto
|
|
2810
|
+
that very key, so a platform-served dashboard arrived pre-translated. But the
|
|
2811
|
+
limb where a bundle entry OVERRIDES the authored value needs the dashboard name,
|
|
2812
|
+
and neither dispatch site handed the component anything but `widget` and
|
|
2813
|
+
`dataSource`.
|
|
2814
|
+
|
|
2815
|
+
The visible effect: a dashboard loaded from an app bundle, whose sub-caption was
|
|
2816
|
+
written only in the i18n bundle and not as `options.description`, rendered
|
|
2817
|
+
nothing on a dataset-bound tile — a shape the field's own documentation calls
|
|
2818
|
+
legitimate ("A translation with no authored counterpart is legitimate and
|
|
2819
|
+
matches the server"). A tile that declared both rendered the untranslated
|
|
2820
|
+
authored string instead of the translation.
|
|
2821
|
+
|
|
2822
|
+
The resolution now happens once, in a new `useWidgetSubCaption` hook, and BOTH
|
|
2823
|
+
dashboard surfaces (`DashboardRenderer` and `DashboardGridLayout` — both route a
|
|
2824
|
+
dataset-bound widget to the same component, objectui#4614) pass the resolved
|
|
2825
|
+
answer down. One decision point, because the field's invariant is that its two
|
|
2826
|
+
channels can never disagree.
|
|
2827
|
+
|
|
2828
|
+
**No behaviour changes for a tile that declares no bundle entry.** The authored
|
|
2829
|
+
limb is untouched, an inline per-locale map still collapses through the same
|
|
2830
|
+
`pickLocalized` seam, and a tile with neither channel still grows no caption
|
|
2831
|
+
node at all. `DatasetWidget` rendered outside a dashboard surface resolves the
|
|
2832
|
+
authored limb for itself exactly as before.
|
|
2833
|
+
|
|
2834
|
+
`@object-ui/sdui-parser` is touched in the same change but releases nothing new: the
|
|
2835
|
+
renderer-consumed-keys census watches `plugin-dashboard`'s source text from inside that
|
|
2836
|
+
package, so moving the sub-caption read moved its evidence anchor. Only the census test
|
|
2837
|
+
and one prose paragraph in the census module's header changed there — no runtime
|
|
2838
|
+
behaviour, no key set, no diagnostic.
|
|
2839
|
+
- db0e9d3: fix(plugin-dashboard): a dimensionless `column` / `horizontal-bar` draws every measure; the dropped-measure warning speaks whenever the widget's own branch leaves a declared measure off the screen; the widget panel stops offering a measure the widget door refuses (objectui#8894)
|
|
2840
|
+
|
|
2841
|
+
**`column` and `horizontal-bar` draw every measure of a dimensionless widget.** They are
|
|
2842
|
+
the bar family in another orientation, so the spec's "the chart families render one mark
|
|
2843
|
+
per measure" covers them, and they now take the same measure transposition `bar` took in
|
|
2844
|
+
objectui#11261: one mark per measure, the measures' labels along the bottom on a `column`
|
|
2845
|
+
and down the side on a `horizontal-bar`. Before, a `horizontal-bar` over revenue, cost and
|
|
2846
|
+
margin with no dimension rendered as a one-number tile showing revenue alone. A
|
|
2847
|
+
single-measure dimensionless widget keeps the tile, as on every type.
|
|
2848
|
+
|
|
2849
|
+
**The dropped-measure warning fires on what the widget's own branch does not render.**
|
|
2850
|
+
`DatasetWidget` derives the measures it renders once, from its branch decision, and both
|
|
2851
|
+
the tile and the console warning read that derivation; the warning speaks when a declared
|
|
2852
|
+
measure is not among them, never from a list of types. Two shapes take the tile with
|
|
2853
|
+
several measures: a metric-family tile stored before `@objectstack/spec` 17.5.0 refused
|
|
2854
|
+
that shape at its door, and a dimensionless widget of a type the spec gives no rendering
|
|
2855
|
+
of several measures (`pie`, `donut`, `funnel`, `scatter`, `radar`, `treemap`, `sankey`),
|
|
2856
|
+
which takes the tile until the spec refuses that shape too. The warning is a diagnostic:
|
|
2857
|
+
it names the widget, the measures it renders and the ones it queried and never displayed,
|
|
2858
|
+
and points to the `replacement` of the spec's ADR-0087 entry
|
|
2859
|
+
`dashboard-widget-metric-family-multi-measure-refused`. It names no widget types and
|
|
2860
|
+
gives no advice of its own. A table, a chart that draws every measure and a
|
|
2861
|
+
single-measure tile stay silent. A dimensioned `pie`, `donut`, `funnel`, `treemap` or
|
|
2862
|
+
`sankey` with several measures draws only the first, and the warning names the measures
|
|
2863
|
+
it drops there too (objectui#11417).
|
|
2864
|
+
|
|
2865
|
+
**`WidgetConfigPanel`'s measure picker follows the widget door.** It asks objectui's
|
|
2866
|
+
`DashboardWidgetSchema` (which re-attaches the spec's own measure check) instead of
|
|
2867
|
+
carrying a list of types: when one more measure would be refused, as on any
|
|
2868
|
+
metric-family widget that already has one, the add control is not offered; when the
|
|
2869
|
+
widget's measures are already refused (a stored document, or a type switched under
|
|
2870
|
+
them), the door's own message is shown under them at once rather than at publish. Every
|
|
2871
|
+
other widget type keeps offering further measures exactly as before.
|
|
2872
|
+
- 0c5cf8b: A dataset-bound metric tile now warns when it drops measures it was told to show
|
|
2873
|
+
(objectui#8894).
|
|
2874
|
+
|
|
2875
|
+
Declaring three measures on a `metric` / `kpi` / `gauge` / `solid-gauge` / `bullet`
|
|
2876
|
+
widget was legal until `@objectstack/spec` 17.5.0, which refuses a second measure on
|
|
2877
|
+
that family at its door; a document stored before then still reaches the tile. So does
|
|
2878
|
+
a widget with no dimensions whose type does not render several measures. The query
|
|
2879
|
+
runs every declared measure, and `DatasetWidget`'s tile rendered the first and stopped
|
|
2880
|
+
there in silence: no warning, no console message, no visual tell. A tile answering a
|
|
2881
|
+
narrower question than its metadata asked read as a finished product, which is the
|
|
2882
|
+
hard part of the defect — not the numbers that never appeared. That is the ADR-0049
|
|
2883
|
+
declared-but-unenforced shape, closed here the same way `warnSuppressedListNav`
|
|
2884
|
+
(ADR-0047, objectui#2338) closes it for a suppressed list-view control.
|
|
2885
|
+
|
|
2886
|
+
**Nothing renders differently on the tile.** Its markup is byte-for-byte what it was,
|
|
2887
|
+
the measures after the first are still dropped, and a tile declaring exactly one
|
|
2888
|
+
measure says nothing at all. Rendering `values[1..]` on a tile is not part of this
|
|
2889
|
+
change: ruling D on objectui#8894 judged the protocol wrong for the metric family and
|
|
2890
|
+
narrowed the spec instead.
|
|
2891
|
+
|
|
2892
|
+
The warning names the widget, its dataset, the measures it renders and each measure
|
|
2893
|
+
it queried and never displayed, and points to the spec's ADR-0087 replacement for a
|
|
2894
|
+
one-number tile with several measures. Its wording is deliberate: the dropped
|
|
2895
|
+
measures are **queried and then never displayed**, not "ignored". They are computed
|
|
2896
|
+
by the server, they join the widget's refetch signature, and `options.sortBy` accepts
|
|
2897
|
+
any of them — which on a `metric`-typed widget that also declares dimensions decides
|
|
2898
|
+
which row is read. Only the display drops them.
|
|
2899
|
+
- 0ab1de2: An authored drill `columns` whitelist no longer degrades the headers of the table it
|
|
2900
|
+
narrows (objectui#9000).
|
|
2901
|
+
|
|
2902
|
+
`ObjectDataTable` derives its column headers on two branches of one memo, and both call
|
|
2903
|
+
the same `fieldLabel` lookup. Only the fallback differed: the auto-derived branch fell
|
|
2904
|
+
back to the humanized field key, the explicit-whitelist branch fell back to whatever
|
|
2905
|
+
`header` the caller supplied. Both drill callers build `{ accessorKey: c, header: c }`
|
|
2906
|
+
out of a `drillDown.columns` string list, so that fallback was the raw field key — and
|
|
2907
|
+
with no bundle entry to resolve, which is the ordinary case in a drill, authoring
|
|
2908
|
+
`columns` rendered `close_date` where the same table without a whitelist rendered
|
|
2909
|
+
`Close Date`. The more deliberate configuration produced the less finished table.
|
|
2910
|
+
|
|
2911
|
+
The whitelist branch now falls back to the same humanized key the auto-derived branch
|
|
2912
|
+
uses. **Visible change:** a drill-through list configured with `drillDown.columns` (the
|
|
2913
|
+
pivot, metric and chart drill surfaces) renders humanized headers where it previously
|
|
2914
|
+
rendered raw field names. Nothing else about the whitelist changes — it still narrows
|
|
2915
|
+
the table to exactly the listed columns, in the listed order.
|
|
2916
|
+
|
|
2917
|
+
The fallback is **conditional**, and deliberately so: it fires only where the column
|
|
2918
|
+
carries no display text, or carries the raw field key itself. An author-written label —
|
|
2919
|
+
`{ accessorKey, header }` or the spec-canonical `{ field, label }`, both of which
|
|
2920
|
+
`normalizeColumns` resolves onto the same key, and both of which `DashboardRenderer`
|
|
2921
|
+
forwards here verbatim from an authored widget — is left exactly as authored. A
|
|
2922
|
+
translated bundle entry still wins over both, unchanged.
|
|
2923
|
+
- 526fc11: `object-metric` refuses `drillDown.filter` and `drillDown.mode` on its prop type (objectui#9002)
|
|
2924
|
+
|
|
2925
|
+
Refused at the TypeScript door only. A stored JSON metric config carrying either key is
|
|
2926
|
+
still accepted and ignored at render, as it was before: no runtime validator in this
|
|
2927
|
+
repository reads the members of an `object-metric` `drillDown`.
|
|
2928
|
+
|
|
2929
|
+
Neither key ever had a read site on a metric. A metric is one aggregated number, so it has
|
|
2930
|
+
no clicked point for a drill `filter` to interpolate `${event.*}` against, and no row for
|
|
2931
|
+
`mode` to open as a record. Its drilled list is always scoped by the block's own `filter`,
|
|
2932
|
+
which is the registration's promise that the number and the records behind it agree. Both
|
|
2933
|
+
keys still type-checked on the widget's `drillDown` prop and then did nothing, with no
|
|
2934
|
+
diagnostic.
|
|
2935
|
+
|
|
2936
|
+
- `@object-ui/types` adds `ObjectMetricDrillDownConfig`, published on the root entry
|
|
2937
|
+
`@object-ui/types` and on the `@object-ui/types/data-display` subpath: `DrillDownConfig`
|
|
2938
|
+
with `filter?: never` and
|
|
2939
|
+
`mode?: never` tombstones whose docblocks name the blocks that do read each key
|
|
2940
|
+
(`object-chart` and `object-pivot` for `filter`, `object-data-table` for `mode`). The
|
|
2941
|
+
shared `DrillDownConfig` is unchanged and keeps both keys for those blocks.
|
|
2942
|
+
- `@object-ui/plugin-dashboard` types `ObjectMetricWidget`'s `drillDown` prop with it, and the
|
|
2943
|
+
`object-metric` registration's `drillDown` description now says in one sentence that the
|
|
2944
|
+
two keys do not apply to a metric and where they do.
|
|
2945
|
+
|
|
2946
|
+
TSX code that passes `filter` or `mode` inside `ObjectMetricWidget`'s `drillDown` now fails
|
|
2947
|
+
to compile. Delete the key: the drilled list was already scoped by the metric's own
|
|
2948
|
+
`filter`, and nothing read `mode`. Runtime behaviour is unchanged.
|
|
2949
|
+
- 9b8bb84: Compose an `ObjectPivotTable` drill-down filter instead of spreading it, so the pivot's own
|
|
2950
|
+
filter survives into the drilled query for BOTH dialects it reads (objectui#9024).
|
|
2951
|
+
|
|
2952
|
+
**The defect.** The drill seam composed the pivot's filter with the click context by spreading
|
|
2953
|
+
the first into an object literal — the identical statement objectui#8944 removed from
|
|
2954
|
+
`ObjectChart` one block over. Spreading an array yields index keys, so a pivot scoped by
|
|
2955
|
+
`[['region','=','emea']]` drilled as `{ '0': ['region','=','emea'], stage: 'won', source: 'web' }`
|
|
2956
|
+
— the pivot's conditions replaced by a stray index key. Nothing errored; the drawer opened and
|
|
2957
|
+
looked right. Measured at the two sinks: the URL writer skips a bare array comparand, so the list
|
|
2958
|
+
landed scoped by the clicked cell ALONE (the superset), and the in-memory matcher refuses that
|
|
2959
|
+
comparand and returns nothing at all. Neither carries the pivot's own conditions.
|
|
2960
|
+
|
|
2961
|
+
**Direction of the failure.** The pivot's filter is what narrows. Dropping it made the drilled
|
|
2962
|
+
list a **superset** — it showed records the pivot itself was scoped to exclude.
|
|
2963
|
+
|
|
2964
|
+
**Why the array arm is reachable here.** `PivotTableSchema` declares no `filter` at all (the
|
|
2965
|
+
type that admits the value is a local props-intersection `filter?: any`), so nothing refuses the
|
|
2966
|
+
array form — and it is not only a hand author who can produce it. `object-pivot`'s registry entry
|
|
2967
|
+
advertises `{ name: 'filter', type: 'array' }` in its designer inputs, and `ElementDataSourceGate`
|
|
2968
|
+
writes the array arm mechanically: binding a pivot the way the spec documents
|
|
2969
|
+
(`dataSource: { object, view }`) sets `schema.filter` to `mergeFilterNodes(schema.filter,
|
|
2970
|
+
view.filter)`, an ObjectQL AST node, for every saved view that carries a filter. So the pre-fix
|
|
2971
|
+
drill dropped the scope of every view-bound pivot, not just of a pivot whose author happened to
|
|
2972
|
+
pick the array spelling.
|
|
2973
|
+
|
|
2974
|
+
**The fix.** `composeDrillFilter` (`@object-ui/core`, added by objectui#8944) already names and
|
|
2975
|
+
applies the rule — `widget.filter` conjoined with `drill.filter`, via the repo's single filter
|
|
2976
|
+
sink `mergeFilterNodes` — and lowers the result back to the object dialect both of this widget's
|
|
2977
|
+
drill sinks take. Routing this site through it replaces the local spread with the shared answer
|
|
2978
|
+
rather than deriving a second one.
|
|
2979
|
+
|
|
2980
|
+
**Compatibility.** The drilled ROW SET is unchanged wherever it was already correct, which is the
|
|
2981
|
+
oracle this change is measured by. The composed VALUE can change shape: a pivot cell click derives
|
|
2982
|
+
two conditions (row field and column field), which are themselves a conjunction, so a pivot with no
|
|
2983
|
+
filter of its own now drills by `{ $and: [{ stage: 'won' }, { source: 'web' }] }` where the spread
|
|
2984
|
+
produced one flat object. Every sink accepts both — `$filter` takes `$and`, and
|
|
2985
|
+
`serializeDrillFilterParams` flattens nested `$and` into the same `filter[...]` params it always
|
|
2986
|
+
wrote (pinned since objectui#8944). When neither source carries anything the seam now answers
|
|
2987
|
+
`undefined` rather than `{}`; `DrillDownDrawer.filter` is optional, so the drilled list is unscoped
|
|
2988
|
+
either way.
|
|
2989
|
+
- 5e9b502: Fix `DashboardGridLayout` composing chart series with no `label` at all
|
|
2990
|
+
(objectui#9172), so a grid-laid-out dashboard legended a measure like
|
|
2991
|
+
`close_date` under the raw field key while the read-only `DashboardRenderer`
|
|
2992
|
+
relay legended the same widget "Close Date". The three-arm label decision
|
|
2993
|
+
(synthetic aggregate name / bundle-or-humanized field label) is now
|
|
2994
|
+
`composeSeriesLabel` (`packages/plugin-dashboard/src/utils.ts`), a single
|
|
2995
|
+
authority both relays call, rather than a second copy of the logic.
|
|
2996
|
+
- a78cd37: Dates and numbers across the console and the plugins format in the session's
|
|
2997
|
+
display locale instead of the machine's (objectui#9909).
|
|
2998
|
+
|
|
2999
|
+
Every one of these faces handed `Intl` — directly, or through a formatter called
|
|
3000
|
+
without one — either no locale tag or an explicit `undefined`, which is not "the
|
|
3001
|
+
user's locale": it is the locale of the machine the browser runs on, which is
|
|
3002
|
+
neither of this repository's two locale channels.
|
|
3003
|
+
A German or Spanish session therefore read, beside a translated label, a date or
|
|
3004
|
+
an amount grouped and decimal-marked the machine's way — and an amount with
|
|
3005
|
+
inverted separators does not read as unformatted, it reads as a different
|
|
3006
|
+
number. They now go through `useDisplayLocale()` (tenant regional default, then
|
|
3007
|
+
the active UI language, then `'en'`), and a plain helper takes that tag from its
|
|
3008
|
+
caller. The surfaces:
|
|
3009
|
+
|
|
3010
|
+
- **`@object-ui/fields`** — `GridField`'s numeric and currency cells (list mode
|
|
3011
|
+
and computed columns) and both total cells. The same cell helper's date branch
|
|
3012
|
+
already used the display locale, so a single grid row read two conventions at
|
|
3013
|
+
once.
|
|
3014
|
+
- **`@object-ui/plugin-charts`** — ISO-date x-axis ticks, compact y-axis ticks,
|
|
3015
|
+
the single-value face, spec `format` strings and the tooltip value.
|
|
3016
|
+
- **`@object-ui/app-shell`** — the organization invitations, members and
|
|
3017
|
+
accept-invitation dates; marketplace version dates; the AI conversation row's
|
|
3018
|
+
older-than-a-week date and its tooltip; the build-debug token count; the
|
|
3019
|
+
record approvals timeline; and the metadata-admin audit, history, external
|
|
3020
|
+
datasource snapshot, schema-browser row estimate, flow-run start and job
|
|
3021
|
+
next-fire faces. The audit, history and flow-run panels format these in the
|
|
3022
|
+
DISPLAY locale, not in the `locale` prop they take for their UI strings
|
|
3023
|
+
(`AuditPanel` defaults that prop to `'en-US'`; `HistoryPanel` and
|
|
3024
|
+
`FlowRunsPanel` leave it optional).
|
|
3025
|
+
- **`@object-ui/console`** — the approvals inbox's timestamp tooltips, amounts
|
|
3026
|
+
and payload summary; the audit log's timestamp column; the flow runs table and
|
|
3027
|
+
its run detail.
|
|
3028
|
+
- **`@object-ui/components`** — the export dialog's record counts and the debug
|
|
3029
|
+
panel's event times.
|
|
3030
|
+
- **`@object-ui/plugin-form`** — the analytics submission count, the
|
|
3031
|
+
master-detail subtotal / tax / total stack and the edit-conflict dialog's
|
|
3032
|
+
"their save" time.
|
|
3033
|
+
- **`@object-ui/plugin-chatbot`** — the approvals inbox's past-30-days date and
|
|
3034
|
+
the times stamped on local-mode chat messages (the user's and the
|
|
3035
|
+
auto-response).
|
|
3036
|
+
- **`@object-ui/plugin-dashboard`** — the record-count badge, and the currency
|
|
3037
|
+
and date-format branches of `renderFieldValue`, which was handed the display
|
|
3038
|
+
locale and spent it only on its percent branch.
|
|
3039
|
+
- **`@object-ui/plugin-grid`** — the record-detail panel's inferred currency
|
|
3040
|
+
value and the mobile card's amount line, siblings of date cells already on the
|
|
3041
|
+
display locale.
|
|
3042
|
+
- **`@object-ui/plugin-designer`** — `VersionHistory`'s version times.
|
|
3043
|
+
|
|
3044
|
+
A host that mounts one of the components that newly read the display locale with
|
|
3045
|
+
NO `I18nProvider` now gets react-i18next's once-per-module `NO_I18NEXT_INSTANCE`
|
|
3046
|
+
notice in the console on the first such mount; the faces still render, in the
|
|
3047
|
+
channel's `'en'` last resort, and mounting an `I18nProvider` (as the console
|
|
3048
|
+
does) avoids the notice.
|
|
3049
|
+
|
|
3050
|
+
**Additive API** (nothing that compiled before stops compiling):
|
|
3051
|
+
|
|
3052
|
+
- `@object-ui/types`: `ValidationContext` gains an optional `locale`. At this
|
|
3053
|
+
change, the `@object-ui/core` validation engine prints the `date_min` /
|
|
3054
|
+
`date_max` bound in it; omitted, `Intl` follows the runtime default, as
|
|
3055
|
+
`formatDisplayNumber` already declares for a caller with no locale in hand.
|
|
3056
|
+
|
|
3057
|
+
⚠️ **Dated note, 2026-09-25 — that engine (`ValidationEngine`) is removed from
|
|
3058
|
+
`@object-ui/core` by a later change; `ValidationContext.locale` stays declared in
|
|
3059
|
+
`@object-ui/types`, and at that change nothing in `@object-ui/core` reads it —
|
|
3060
|
+
objectui#7659.**
|
|
3061
|
+
|
|
3062
|
+
⚠️ **Dated note, 2026-09-27 — `ValidationContext` itself is removed from
|
|
3063
|
+
`@object-ui/types` by a later change, with the rest of the Phase 3.5
|
|
3064
|
+
validation types it belonged to, so the `locale` member this entry adds no
|
|
3065
|
+
longer exists — objectui#10719.**
|
|
3066
|
+
- `@object-ui/plugin-report`: `exportReport`, `exportAsHTML` and `exportAsPDF`
|
|
3067
|
+
take a trailing optional `locale` for the exported file's "Generated:" time,
|
|
3068
|
+
and `LiveExportOptions` gains an optional `locale` that `exportWithLiveData`
|
|
3069
|
+
forwards. Omitted, the time uses the display channel's own last resort
|
|
3070
|
+
(`'en'`), never the machine's locale. `ReportViewer` passes the session's
|
|
3071
|
+
display locale. The locale is deliberately NOT a `ReportExportConfig` member:
|
|
3072
|
+
that type is authored report metadata, and a display locale belongs to the
|
|
3073
|
+
session.
|
|
3074
|
+
|
|
3075
|
+
**One census, repository-wide.** `plugin-detail`'s machine-locale census pin
|
|
3076
|
+
(objectui#9786) is now a single test, `machineLocaleCensus-9909.test.ts` in
|
|
3077
|
+
`@object-ui/i18n`, covering every workspace package whose manifest depends on
|
|
3078
|
+
`@object-ui/i18n`. It refuses any call site that passes nothing, `undefined`,
|
|
3079
|
+
the `'default'` pseudo-tag (a subtag no locale data answers, so `Intl` resolves
|
|
3080
|
+
it to the machine's locale) or a hard-coded tag, unless the site is declared
|
|
3081
|
+
with its reason — for example the `catch` fallback for a tag `Intl` itself
|
|
3082
|
+
rejected, or an ISO formatter feeding `<input type="date">`. Each declared site
|
|
3083
|
+
is counted exactly, so deleting one without its entry is refused too. The
|
|
3084
|
+
per-package pin it replaces is deleted. The runtime tripwire that observes the
|
|
3085
|
+
argument each locale-taking call (`Intl` constructors, `Date` and `Number`
|
|
3086
|
+
`toLocale*`) actually receives moved into the private `@object-ui/test-support`
|
|
3087
|
+
package, and every surface above is pinned with it: the same data under two
|
|
3088
|
+
declared locales must render differently, NO locale-taking call may receive the
|
|
3089
|
+
machine's locale (nothing, `undefined` or `'default'`), and the surface's own
|
|
3090
|
+
calls must receive the declared tag. A call carrying another declared tag, such
|
|
3091
|
+
as the `'en'` of the session's UI language, is tolerated by design: only the
|
|
3092
|
+
machine's locale is the defect.
|
|
3093
|
+
- 3d79455: `plugin-dashboard`'s two private copies of the reference-bearing field family now
|
|
3094
|
+
read `@object-ui/core`'s published `EXPANDABLE_FIELD_TYPES` instead of restating
|
|
3095
|
+
it — `LOOKUP_TYPES` in `recordFields.tsx` and the inline disjunction inside
|
|
3096
|
+
`computeLookupExpand` in `ObjectDataTable.tsx`. Both carry the identity pin the
|
|
3097
|
+
already-converged consumers carry, so a member-identical private copy fails
|
|
3098
|
+
rather than quietly re-forking the table.
|
|
3099
|
+
|
|
3100
|
+
Two behaviour changes on the dashboard table's `$expand`, in opposite directions:
|
|
3101
|
+
|
|
3102
|
+
- **A `tree` column is now expanded.** A self-referencing hierarchy field is
|
|
3103
|
+
reference-bearing and a member of the shared family, so its cell renders the
|
|
3104
|
+
parent record's display name instead of a bare id — the same treatment the form
|
|
3105
|
+
and grid roads already gave it.
|
|
3106
|
+
- **A `reference`-typed column is no longer expanded.** Measured before removing
|
|
3107
|
+
it: `reference` is absent from `@objectstack/spec`'s closed `FieldType`
|
|
3108
|
+
vocabulary and is refused by `FieldSchema.safeParse`, so no spec-compliant
|
|
3109
|
+
object schema can declare a field whose stored type is `reference`. Dropping it
|
|
3110
|
+
is a no-op on real data; the spelling is a legacy dialect alias on the
|
|
3111
|
+
action-param surface, folded to `lookup` before any field-type data is read.
|
|
3112
|
+
|
|
3113
|
+
`EXPANDABLE_FIELD_TYPES` itself is unchanged — the measurement did not license
|
|
3114
|
+
widening a published shared set.
|
|
3115
|
+
- cc5de5c: `DashboardRenderer` no longer emits an empty dashboard header wrapper. The
|
|
3116
|
+
wrapper used to render whenever `header` was declared, while each of its
|
|
3117
|
+
children — title, description, actions — was additionally suppressible. With
|
|
3118
|
+
the console page chrome present (`hideHeaderText`, set because the chrome
|
|
3119
|
+
already renders the dashboard's title and description) and no `header.actions`,
|
|
3120
|
+
every child evaluated falsy and the DOM still received
|
|
3121
|
+
`<div class="col-span-full mb-4"></div>`: zero children, yet a full grid row
|
|
3122
|
+
(measured 64px) plus `mb-4` of dead band above the filter bar, on every console
|
|
3123
|
+
dashboard page (objectui#5812, measured on HotCRM 17.1.0).
|
|
3124
|
+
|
|
3125
|
+
The three children are now computed first and the wrapper renders only if one
|
|
3126
|
+
of them survives. Nothing else changes: a standalone embed (no chrome) renders
|
|
3127
|
+
title and description exactly as before, and declared `header.actions` keep the
|
|
3128
|
+
wrapper alive even under the chrome, since the chrome renders text only. Authors
|
|
3129
|
+
needed this — dropping `header` from the metadata to reclaim the pixels would
|
|
3130
|
+
have cost the standalone embed its title, which is what `header` is for.
|
|
3131
|
+
- 84d2e98: `DashboardRenderer` drops the unreachable `DatasetWidget` fork from its self-contained
|
|
3132
|
+
(card-less) branch, leaving that branch to render `SchemaRenderer` unconditionally
|
|
3133
|
+
(objectui#4620).
|
|
3134
|
+
|
|
3135
|
+
`isSelfContained` is defined as `widget.type === 'metric' && !datasetBound`, and the
|
|
3136
|
+
`isSelfContained` arm of `renderedNode` then forked on `datasetBound` a second time. The
|
|
3137
|
+
`datasetBound` side of that inner fork could never execute: reaching it required
|
|
3138
|
+
`isSelfContained` to be true, which requires `!datasetBound`. Behaviour is unchanged —
|
|
3139
|
+
the removed arm never ran, and the reachable fork in the Card branch (the one that gives
|
|
3140
|
+
a dataset-bound metric its title and border chrome) is untouched.
|
|
3141
|
+
|
|
3142
|
+
The cost was to readers, not to users: the shape read as "both branches handle
|
|
3143
|
+
dataset-bound widgets" when only one can, and a previous PR mirroring this fork onto
|
|
3144
|
+
`DashboardGridLayout` had to pay for the reachability argument before it could decline to
|
|
3145
|
+
copy the dead limb. A comment now names the invariant in place so the arm is not re-added.
|
|
3146
|
+
- f24195a: `ObjectDataTable` and `ObjectPivotTable` now use a module-scope frozen empty for
|
|
3147
|
+
"no rows yet" instead of a fresh array literal per render (objectui#4629).
|
|
3148
|
+
|
|
3149
|
+
Both spelled the resolved row list as `Array.isArray(rawData) ? rawData : []`, so
|
|
3150
|
+
whenever `rawData` was a truthy non-array — a provider-config `data`, or a `bind`
|
|
3151
|
+
path that resolves to an object — the fallback produced a NEW array identity on
|
|
3152
|
+
every render. In `ObjectDataTable` that value keys the `derivedColumns` memo, so
|
|
3153
|
+
every column was re-derived (`buildFieldMeta`, a fresh `cell` closure, the
|
|
3154
|
+
`isSystemField` pass, the `fieldLabel` lookups) and then discarded by the
|
|
3155
|
+
`finalData.length === 0` early return. In `ObjectPivotTable` the value is handed
|
|
3156
|
+
straight to `PivotTable`, where it keys the cross-tabulation memo, so the pivot
|
|
3157
|
+
rebuilt its row/column sets, bucket map and totals on every render over no rows
|
|
3158
|
+
at all.
|
|
3159
|
+
|
|
3160
|
+
Nothing rendered wrong before or after; this is wasted work in the empty window,
|
|
3161
|
+
plus the live `react-hooks/exhaustive-deps` warning the conditional raised. It is
|
|
3162
|
+
the same module-scope frozen empty `data-table.tsx` adopted for its own
|
|
3163
|
+
`EMPTY_ROWS` (objectui#4618), applied to the `provider: 'object'` siblings.
|
|
3164
|
+
- 56f4e34: A dashboard table's auto-derived column headers spell a field key the same way every other path in the `table` widget family does.
|
|
3165
|
+
|
|
3166
|
+
`ObjectDataTable` derives headers on two paths — from the author's declared
|
|
3167
|
+
`columns`, and from the object schema when no columns were declared. The
|
|
3168
|
+
declared path (and the static `data-table` half of the same widget family)
|
|
3169
|
+
already used `humanizeFieldKey`, whose docstring names it "the single home for
|
|
3170
|
+
the convention, because both halves of the `table` widget family need it and
|
|
3171
|
+
they must agree". The auto-derived path carried a third, inline spelling that
|
|
3172
|
+
split camelCase but never turned `_` into a space, so it left a raw underscore
|
|
3173
|
+
on screen. Measured over the same object's columns:
|
|
3174
|
+
|
|
3175
|
+
```
|
|
3176
|
+
path close_date needs_analysis
|
|
3177
|
+
object-bound, AUTO-DERIVED (before) Close_date Needs_analysis
|
|
3178
|
+
object-bound, AUTO-DERIVED (after) Close Date Needs Analysis
|
|
3179
|
+
object-bound, DECLARED columns Close Date Needs Analysis
|
|
3180
|
+
static `data-table`, no columns Close Date Needs Analysis
|
|
3181
|
+
```
|
|
3182
|
+
|
|
3183
|
+
One dashboard can hold all three widgets over one object, so a single field key
|
|
3184
|
+
rendered under two spellings — the defect class this change rules out. The odd
|
|
3185
|
+
path adopts the shared convention rather than the convention gaining a fourth
|
|
3186
|
+
dialect. camelCase keys are unaffected (`unitPrice` read `Unit Price` before and
|
|
3187
|
+
after — the coincidence that kept the snake_case divergence unnoticed), and a
|
|
3188
|
+
translated header still wins: only the fallback handed to `fieldLabel` changed.
|
|
3189
|
+
|
|
3190
|
+
Dimension MEMBER labels are untouched by this. The same card reported dashboard
|
|
3191
|
+
members rendering a prettified enum instead of the picklist's translated label,
|
|
3192
|
+
measured on 17.1.0; re-measured on this branch it no longer reproduces — the
|
|
3193
|
+
analytics label net shipped in 17.5.0 routes every non-metric dataset dimension
|
|
3194
|
+
through the field's declared options and the locale bundle. That behaviour had
|
|
3195
|
+
no test stated in the card's terms and now has one, over the four dashboards the
|
|
3196
|
+
card measured, including the property that a bar axis and a pivot header cannot
|
|
3197
|
+
disagree about one stored value.
|
|
3198
|
+
- b9cefe6: One field key now renders under one spelling across a dashboard drill chain: the record drawer and the chart series label both derive their display string with `humanizeFieldKey`, the single home for the KEY convention (objectui#9055).
|
|
3199
|
+
|
|
3200
|
+
**The record drawer, on the same click.** `RecordDetailDrawer` built its field label from a fourth inline spelling that upper-cased only the FIRST word. `ObjectDataTable`'s drill list is drill-to-record by default and the record it opens is this drawer, so one click moved from a column headed `Close Date` to a field labelled `Close date` — same key, same widget, one uninterrupted interaction. Measured on the rendered DOM of one chain, before the fix: headers `Close Date · Needs Analysis · Unit Price · Amount` against drawer labels `Close date · Needs analysis · Unit Price · Amount`. camelCase agreed under both spellings, which is why it went unnoticed.
|
|
3201
|
+
|
|
3202
|
+
**The chart series label, on two of its three arms.** `resolveSeriesLabel` in `DashboardRenderer` handed back the RAW field key twice: as `fieldLabel`'s fallback on the object-bound arm, and outright on the arm every static-data chart takes. A chart grouped on `close_date` legended it `close_date` while the table widget beside it headed that column `Close Date`. The static-data arm is not named by the card; it is the same defect reached through the other call site, and it is fixed here rather than left for a follow-up. The synthetic-key arm — a count aggregation with no real field, which resolves an i18n'd aggregate name (Count / 计数) — is a different vocabulary and is deliberately untouched.
|
|
3203
|
+
|
|
3204
|
+
The i18n wrapper is unchanged everywhere: a bundle entry still wins and the derived spelling is only its fallback, and the `objectName` guards are as they were. No published export, schema key or registry entry moves. The KEY prefixer stays distinct from `humanizeLabel`, the VALUE prefixer in `@object-ui/core` — converging those two is a separate decision its own docblock already reserves for its own card.
|
|
3205
|
+
- fb336df: Move `lucide-react` from `^1.31.0` to `^1.43.0` in every package that declares it, and
|
|
3206
|
+
repair what the jump breaks, so icons resolved from a STRING keep drawing a glyph.
|
|
3207
|
+
|
|
3208
|
+
Measured once against the installed 1.43.0 artifact when this change was made; nothing in
|
|
3209
|
+
the repository re-derives these readings. Across the jump lucide removes no runtime export,
|
|
3210
|
+
no public type name and no `lucide-react/dynamic.mjs` name, and every name this repository
|
|
3211
|
+
imports from `lucide-react` resolves. Exactly one key leaves the runtime `icons` record:
|
|
3212
|
+
`Trash2`, retired in favour of `trash`.
|
|
3213
|
+
|
|
3214
|
+
What a user sees change:
|
|
3215
|
+
|
|
3216
|
+
- Icons the lazy icon seam draws (`resolveIcon`, objectui#9251) get their path data again.
|
|
3217
|
+
lucide 1.43.0 icon modules export their path data inside `__iconData` and no longer
|
|
3218
|
+
export `__iconNode`; the seam now reads both. Reading only `__iconNode` against 1.43.0
|
|
3219
|
+
leaves every such glyph an empty box, with nothing thrown or logged.
|
|
3220
|
+
- `DetailView`'s delete action and three schema-catalog examples spell their icon
|
|
3221
|
+
`trash`, not `trash-2`, so they keep resolving. The glyph is unchanged: 1.43.0's `trash`
|
|
3222
|
+
path data is byte-identical to the `trash-2` path data of 1.31.0 and 1.35.0. Anything
|
|
3223
|
+
that already authored `trash` now draws that same artwork, because lucide moved it under
|
|
3224
|
+
the `trash` name; a few other glyphs were also redrawn upstream.
|
|
3225
|
+
- Icons imported as COMPONENTS carry lucide 1.43.0's own classes: one class per declared
|
|
3226
|
+
alias (a spinner renders `class="lucide lucide-loader-circle lucide-loader-2 ..."`), and
|
|
3227
|
+
no longer the class lucide used to derive from the PascalCase key where that differs
|
|
3228
|
+
(`ArrowDown01` no longer carries `lucide-arrow-down01`). The canonical
|
|
3229
|
+
`lucide-<icon name>` class is still there, so a `.lucide-loader-circle` selector still
|
|
3230
|
+
matches.
|
|
3231
|
+
- Icons resolved from a STRING through the seam keep the classes they had: `lucide`, the
|
|
3232
|
+
canonical `lucide-<icon name>` class and the key-derived class. They do not carry
|
|
3233
|
+
lucide's per-alias classes. That is a maintainer ruling (objectui#8941, option C): the
|
|
3234
|
+
alias list lives only inside each lazily loaded icon module, and it was not moved into
|
|
3235
|
+
the eager name list. A selector that targets an alias class matches a component-imported
|
|
3236
|
+
icon and not a string-resolved one. This also dates the example in the objectui#9251
|
|
3237
|
+
entry: `trash-2` no longer resolves as a string, so no seam-drawn glyph carries
|
|
3238
|
+
`lucide-trash2 lucide-trash-2`; a digit-bearing name that still resolves, such as
|
|
3239
|
+
`arrow-down-0-1`, carries `lucide-arrow-down01 lucide-arrow-down-0-1`.
|
|
3240
|
+
- 84d70bb: `ObjectPivotTable` now depends on the `fieldLabel` / `fieldOptionLabel`
|
|
3241
|
+
resolvers directly instead of holding them behind refs, so a pivot re-derives
|
|
3242
|
+
its header and option labels when the resolver genuinely changes
|
|
3243
|
+
(objectui#5625).
|
|
3244
|
+
|
|
3245
|
+
The refs existed for a reason that no longer holds. `useSafeFieldLabel()`
|
|
3246
|
+
returned a fresh object on every render outside an i18next provider, so a direct
|
|
3247
|
+
dependency would have re-run the metadata-derivation effect on every render —
|
|
3248
|
+
and that effect ends in `setFieldLabelMaps` / `setFieldNameLabels` with freshly
|
|
3249
|
+
built objects, so each run scheduled the next one: an unbounded derive loop.
|
|
3250
|
+
`ObjectPivotTable` worked around that locally with `fieldLabelRef` /
|
|
3251
|
+
`fieldOptionLabelRef` plus a `useEffect` keeping them current.
|
|
3252
|
+
`useObjectLabel`'s memo now holds with or without an i18next instance bound
|
|
3253
|
+
(objectui#5564), so both resolvers have a stable identity on both paths and the
|
|
3254
|
+
indirection buys nothing.
|
|
3255
|
+
|
|
3256
|
+
It did cost something, and that is the user-visible half: a ref-hidden
|
|
3257
|
+
dependency meant the derivation did NOT re-run when the resolver changed. A
|
|
3258
|
+
pivot mounted before its `I18nProvider`, or rendered across a language switch,
|
|
3259
|
+
kept showing its top-left header label and its select-option cell labels as
|
|
3260
|
+
resolved by the old resolver — until some unrelated dependency (the data source,
|
|
3261
|
+
the object name) happened to move. It now re-derives once on that transition and
|
|
3262
|
+
renders in the active language.
|
|
3263
|
+
|
|
3264
|
+
This is the same removal objectui#5587 made in `ObjectChart`, one package over.
|
|
3265
|
+
|
|
3266
|
+
Pinned by `ObjectPivotTable.i18nResolverDeps.test.tsx`, which counts derivations
|
|
3267
|
+
across forced re-renders both outside and inside a provider, checks that the two
|
|
3268
|
+
derived state maps settle, and asserts the language-switch re-derivation.
|
|
3269
|
+
Reverting `useObjectLabel.ts` to its pre-objectui#5585 state turns the
|
|
3270
|
+
no-provider case red (derivations in the dozens instead of 1), so the removal is
|
|
3271
|
+
pinned to the fix that unlocked it rather than to a comment.
|
|
3272
|
+
- f1c27f0: Dashboard record fields: percent columns now render through the one percent
|
|
3273
|
+
scaling decision instead of a second, drifted copy of it.
|
|
3274
|
+
|
|
3275
|
+
`renderFieldValue`'s `%`-format branch normalised the value itself before
|
|
3276
|
+
calling `formatPercent` (`const normalized = value > 1 ? value / 100 : value`,
|
|
3277
|
+
then `normalized * 100`). `formatPercent` already applies `percentDisplayValue`,
|
|
3278
|
+
which `@object-ui/core` documents as the single source of truth for percent
|
|
3279
|
+
display scaling, so the branch was re-deciding what core owns — and its copy had
|
|
3280
|
+
drifted from it in three measured ways:
|
|
3281
|
+
|
|
3282
|
+
- `(value / 100) * 100` is not value-preserving in binary floating point,
|
|
3283
|
+
re-introducing one call frame upstream the round trip that was removed from
|
|
3284
|
+
inside `formatPercent`. On the 0.001-step grid to 200, 19,978 of 199,000
|
|
3285
|
+
values change bit pattern and 1,108 rendered strings move, every one a
|
|
3286
|
+
last-digit off-by-one: a stored `1.605` rendered `1.60%` where half-up is
|
|
3287
|
+
`1.61%`.
|
|
3288
|
+
- A stored fraction below `0.01` was scaled twice — the local `* 100` put it
|
|
3289
|
+
back under 1, so core's fraction arm scaled it again. `0.005` (0.5%) rendered
|
|
3290
|
+
`50.00%`.
|
|
3291
|
+
- The local test was `value > 1` rather than core's symmetric `|value| < 1`, so
|
|
3292
|
+
a negative already in percentage points took the fraction arm: `-5` rendered
|
|
3293
|
+
`-500.00%`.
|
|
3294
|
+
|
|
3295
|
+
The branch now hands the raw stored value to `formatPercent` — the identical
|
|
3296
|
+
call the list-view percent cell already makes for an ordinary percent column —
|
|
3297
|
+
so a percent reads the same as a record field, as a grid cell and as a dashboard
|
|
3298
|
+
measure. Output moves where it was wrong: values at or above 1 whose round trip
|
|
3299
|
+
lost a digit, fractions below `0.01`, negatives at or below `-1`, and exactly
|
|
3300
|
+
`1`, which is one percentage point by core's convention and now renders
|
|
3301
|
+
`1.00%` at two decimals, where the local `value > 1` test had made it
|
|
3302
|
+
`100.00%`.
|
|
3303
|
+
|
|
3304
|
+
⚠️ **Dated note, 2026-10-02 — the magnitude guess is retired in this same release — objectui#11475.**
|
|
3305
|
+
Later in this same release `percentDisplayValue` (`@object-ui/core`) stopped
|
|
3306
|
+
inferring a percentage's storage from the value: it takes the storage as a
|
|
3307
|
+
required argument, and every percent face passes the storage the field
|
|
3308
|
+
declares (`percentScaleOf` in `@objectstack/spec/data`: a fraction unless the
|
|
3309
|
+
field declares a `max` above 1). So exactly `1` on a `percent` field that declares no `max` renders `100.00%`,
|
|
3310
|
+
one on a field declaring `max: 100` renders `1.00%`, and a non-percent field
|
|
3311
|
+
with a `%` pattern reads its value as a fraction, numeral's own reading. The rest of this entry is kept as the reading of this change.
|
|
3312
|
+
- 0ccbdc1: `PivotTable` no longer re-runs its cross-tabulation memo on every render when it
|
|
3313
|
+
has no rows (objectui#5562).
|
|
3314
|
+
|
|
3315
|
+
The component spelled the empty array twice — as the destructuring default for
|
|
3316
|
+
`schema.data` and as the `Array.isArray` fallback that keeps a provider-config
|
|
3317
|
+
object out of iteration — so a schema declaring no `data` key, or one whose
|
|
3318
|
+
`data` is a provider config rather than rows, produced a fresh array identity on
|
|
3319
|
+
every render. That value is the first entry of the memo's dependency list, so
|
|
3320
|
+
the memo rebuilt its two ordered key sets, its `bucket[row][col]` map, the
|
|
3321
|
+
aggregated matrix and the row/column/grand totals on every render, over nothing.
|
|
3322
|
+
Both spellings now resolve to one module-scope frozen empty, so "no rows" is a
|
|
3323
|
+
stable value and the memo holds.
|
|
3324
|
+
|
|
3325
|
+
Wasted work only: the churn feeds a memo rather than a `setState`, and
|
|
3326
|
+
`PivotTable` holds no prop-to-state sync, so nothing rendered wrong and no
|
|
3327
|
+
render loop was possible. The identical fix landed for `data-table` in
|
|
3328
|
+
objectui#4618 and for `ObjectPivotTable` in objectui#4629; this closes the
|
|
3329
|
+
direct-use path those two did not cover, where `DashboardRenderer` and
|
|
3330
|
+
`DashboardGridLayout` construct pivot schemas without `ObjectPivotTable` in the
|
|
3331
|
+
chain.
|
|
3332
|
+
- 6c6cee7: A RETIRED field-type spelling is now refused — out loud, once — by every
|
|
3333
|
+
field-type predicate in the renderer, not just by the widget road
|
|
3334
|
+
(objectui#4914, maintainer ruling B of 2026-08-18).
|
|
3335
|
+
|
|
3336
|
+
`@object-ui/fields` exports a single `isRetiredFieldType(t)` gate, and it runs
|
|
3337
|
+
ahead of six predicate faces that previously granted a retired spelling
|
|
3338
|
+
first-class treatment: the filter builder's operator buckets and its value
|
|
3339
|
+
control (`@object-ui/components`), the detail page's highlight-strip picker
|
|
3340
|
+
(`@object-ui/plugin-detail`), `normalizeFieldType` (`@object-ui/plugin-view`),
|
|
3341
|
+
the dashboard's `$expand` whitelist and `isLookupType`
|
|
3342
|
+
(`@object-ui/plugin-dashboard`), and the list toolbar's lookup-like filter
|
|
3343
|
+
control (`@object-ui/plugin-list`). Each one now fires the migration
|
|
3344
|
+
prescription on the console — once per spelling across all of them, never once
|
|
3345
|
+
per predicate — and then answers as it would for a spelling it does not
|
|
3346
|
+
recognise.
|
|
3347
|
+
|
|
3348
|
+
This closes the whole CLASS rather than one word: the gate is quantified over
|
|
3349
|
+
`RETIRED_FIELD_TYPES`, so the next retirement covers all seven consumers on the
|
|
3350
|
+
day it lands. It is the shape objectui#4932 and objectui#4942 already
|
|
3351
|
+
established for the form and inline-edit roads.
|
|
3352
|
+
|
|
3353
|
+
Measured before the change, and the reason the fix is a gate rather than a
|
|
3354
|
+
deletion: `owner` was not dead in these faces. `operatorsForFieldType('owner')`
|
|
3355
|
+
equalled the `user` bucket item for item, `computeLookupExpand` actively
|
|
3356
|
+
requested `$expand` for it, `isLookupType('owner')` was `true` alongside
|
|
3357
|
+
`reference`, and `normalizeFieldType('owner')` answered `'select'` exactly as
|
|
3358
|
+
`picklist` does. Deleting the members alone would have traded a visible
|
|
3359
|
+
contradiction for a SILENT degradation — a filter picker collapsing to a bare id
|
|
3360
|
+
box, `$expand` quietly stopping so cells show raw foreign-key ids — which is
|
|
3361
|
+
verbatim the failure mode `RETIRED_FIELD_TYPES`' own docblock exists to prevent.
|
|
3362
|
+
The gate keeps that fallback and adds the half that was missing: the author is
|
|
3363
|
+
told.
|
|
3364
|
+
|
|
3365
|
+
The boundary question is answered on record: `owner` arriving through a
|
|
3366
|
+
backend-vocabulary normalizer is an authoring error to refuse loudly, not
|
|
3367
|
+
legitimate foreign input to tolerate. The open backend vocabulary those
|
|
3368
|
+
normalizers exist for is untouched — `reference`, `picklist`, `money`, `int`,
|
|
3369
|
+
`datetime_tz` and the rest are equally absent from the spec's closed `FieldType`
|
|
3370
|
+
and are equally unretired, so they classify exactly as before.
|
|
3371
|
+
|
|
3372
|
+
`RETIRED_FIELD_TYPES`, `reportRetiredFieldType` and `resetRetiredFieldTypeReports`
|
|
3373
|
+
move to `@object-ui/core` and are re-exported from `@object-ui/fields`, so that
|
|
3374
|
+
package's published surface is unchanged apart from the newly ruled gate.
|
|
3375
|
+
`@object-ui/components` is a consumer of the gate and `@object-ui/fields`
|
|
3376
|
+
depends on it, so a single shared table could not live in `fields` — and a
|
|
3377
|
+
second copy would have meant a second dedupe set and two console lines for one
|
|
3378
|
+
spelling. No package gained a new dependency.
|
|
3379
|
+
|
|
3380
|
+
A retired spelling never loses a stored value: `retypeFilterValue` is
|
|
3381
|
+
deliberately not gated, and the refused filter row stays operable rather than
|
|
3382
|
+
drawing a blank operator trigger.
|
|
3383
|
+
- cfcff30: Each package's README now states, up front, that it needs a bundler: importing it from plain Node ESM fails, and that is a supported-configuration boundary rather than a defect.
|
|
3384
|
+
|
|
3385
|
+
`@object-ui/plugin-dashboard` imports `react-grid-layout/css/styles.css` at module
|
|
3386
|
+
scope and `@object-ui/plugin-map` imports `maplibre-gl/dist/maplibre-gl.css`;
|
|
3387
|
+
`@object-ui/app-shell` reaches the first of those through the static
|
|
3388
|
+
`@object-ui/plugin-dashboard` imports in `DashboardView` and `ReportView`. Node has
|
|
3389
|
+
no loader for `.css` at all, so all three resolve and then die during evaluation:
|
|
3390
|
+
|
|
3391
|
+
```
|
|
3392
|
+
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css"
|
|
3393
|
+
for .../react-grid-layout/css/styles.css
|
|
3394
|
+
```
|
|
3395
|
+
|
|
3396
|
+
Nothing about how these packages load has changed — every supported host bundles
|
|
3397
|
+
them (Vite, webpack, or Next with the package in `transpilePackages`), and that is
|
|
3398
|
+
still the only supported way to consume them. What changed is that the boundary is
|
|
3399
|
+
now written where a consumer meets it, instead of being learned from a red import.
|
|
3400
|
+
|
|
3401
|
+
objectui#5384 ruled unbundled Node consumption **unsupported** for style-carrying
|
|
3402
|
+
plugin packages — permanently, over the three packages as a group — rather than
|
|
3403
|
+
moving the stylesheet imports out of module scope. No unbundled-Node consumer
|
|
3404
|
+
exists, and buying permanent machinery to close a capability gap nobody is pulling
|
|
3405
|
+
on was the trade the ruling declined. A real consumer request reopens it as a
|
|
3406
|
+
design question, not as a defect: the READMEs say so and name the issue.
|
|
3407
|
+
- fb96ecb: `WidgetConfigPanel` reads an inline-locale-map title, and a save no longer destroys the other locales.
|
|
3408
|
+
|
|
3409
|
+
The dashboard widget config panel carried a private `resolveLabel` documented as
|
|
3410
|
+
resolving an `I18nLabel` while reading `defaultValue || key` — the key-reference
|
|
3411
|
+
form `@objectstack/spec` retired at 17.0.0-rc.6 (objectstack#5055). The inline
|
|
3412
|
+
per-locale map `I18nLabelSchema` actually admits has neither limb, so
|
|
3413
|
+
`{ en: 'Revenue', zh: '收入' }` resolved to `''`. It was the fourth private copy
|
|
3414
|
+
of that resolver; objectui#4032 swept the other three out of `DashboardRenderer`,
|
|
3415
|
+
`MetricWidget` and `MetricCard`.
|
|
3416
|
+
|
|
3417
|
+
This was not a display bug. The resolved value seeds the panel's editable draft,
|
|
3418
|
+
so a widget whose stored title was a map opened with an **empty** Title field and
|
|
3419
|
+
the next save wrote `''` over the author's map — on the ordinary path, not an
|
|
3420
|
+
exotic one: open the widget, change anything, save.
|
|
3421
|
+
|
|
3422
|
+
Both halves are fixed, per the maintainer's 2026-08-20 ruling on objectui#5301:
|
|
3423
|
+
|
|
3424
|
+
- **Reading** goes through `pickLocalized(value, language)`, so the panel shows
|
|
3425
|
+
the active locale like every sibling surface post-objectui#4032.
|
|
3426
|
+
- **Writing** replaces only the active locale's entry and carries every other
|
|
3427
|
+
locale across. A title the author never touched round-trips the stored object
|
|
3428
|
+
itself through an unrelated config edit; an edited one merges into the entry
|
|
3429
|
+
that was displayed. The live-update callback (`onFieldChange`) forwards the
|
|
3430
|
+
merged map for the same reason — hosts feed it back into the widget the panel
|
|
3431
|
+
re-opens from, so a bare string there dropped the map before a save ever ran.
|
|
3432
|
+
|
|
3433
|
+
`@object-ui/i18n` gains `setLocalized(value, language, next)`, the write-side
|
|
3434
|
+
inverse of `pickLocalized`, so the rule is stated once instead of re-derived per
|
|
3435
|
+
panel. It follows `pickLocalized`'s first three limbs — exact tag, base language,
|
|
3436
|
+
region-qualified sibling — and deliberately stops there: the `default` / `en` /
|
|
3437
|
+
first-value limbs are display fallbacks that hand back *another* locale's string,
|
|
3438
|
+
and writing to one would let an author editing in `fr` overwrite English. With no
|
|
3439
|
+
entry for the active locale the edit adds one. The pairing
|
|
3440
|
+
`pickLocalized(setLocalized(map, lang, s), lang) === s` is pinned, because a
|
|
3441
|
+
write that lands where the read does not look is how a "saved" string disappears.
|
|
3442
|
+
|
|
3443
|
+
A full multi-locale editing UI remains out of scope (objectui#4163).
|
|
3444
|
+
- Updated dependencies [7b10bef]
|
|
3445
|
+
- Updated dependencies [97abedc]
|
|
3446
|
+
- Updated dependencies [b46c58f]
|
|
3447
|
+
- Updated dependencies [a507334]
|
|
3448
|
+
- Updated dependencies [ad694ac]
|
|
3449
|
+
- Updated dependencies [6f96fca]
|
|
3450
|
+
- Updated dependencies [afb2284]
|
|
3451
|
+
- Updated dependencies [3ac2de8]
|
|
3452
|
+
- Updated dependencies [0aacecc]
|
|
3453
|
+
- Updated dependencies [63f4f92]
|
|
3454
|
+
- Updated dependencies [777fca2]
|
|
3455
|
+
- Updated dependencies [c131d9e]
|
|
3456
|
+
- Updated dependencies [5f00ff4]
|
|
3457
|
+
- Updated dependencies [c9e073a]
|
|
3458
|
+
- Updated dependencies [0361d6b]
|
|
3459
|
+
- Updated dependencies [7b395d8]
|
|
3460
|
+
- Updated dependencies [0879812]
|
|
3461
|
+
- Updated dependencies [8cedb0d]
|
|
3462
|
+
- Updated dependencies [162621b]
|
|
3463
|
+
- Updated dependencies [4c6f549]
|
|
3464
|
+
- Updated dependencies [80c5412]
|
|
3465
|
+
- Updated dependencies [6cc910b]
|
|
3466
|
+
- Updated dependencies [061f5e8]
|
|
3467
|
+
- Updated dependencies [2dd4d3f]
|
|
3468
|
+
- Updated dependencies [e686f4d]
|
|
3469
|
+
- Updated dependencies [212c451]
|
|
3470
|
+
- Updated dependencies [a04b06d]
|
|
3471
|
+
- Updated dependencies [4ab4f1b]
|
|
3472
|
+
- Updated dependencies [b57107d]
|
|
3473
|
+
- Updated dependencies [e3ea4f9]
|
|
3474
|
+
- Updated dependencies [65f1e8d]
|
|
3475
|
+
- Updated dependencies [1f8ef0a]
|
|
3476
|
+
- Updated dependencies [bb5d4ee]
|
|
3477
|
+
- Updated dependencies [dc666f7]
|
|
3478
|
+
- Updated dependencies [3335767]
|
|
3479
|
+
- Updated dependencies [8b1f066]
|
|
3480
|
+
- Updated dependencies [af243c1]
|
|
3481
|
+
- Updated dependencies [cff4b77]
|
|
3482
|
+
- Updated dependencies [31938f0]
|
|
3483
|
+
- Updated dependencies [961ceaa]
|
|
3484
|
+
- Updated dependencies [f3f4e4c]
|
|
3485
|
+
- Updated dependencies [a05c350]
|
|
3486
|
+
- Updated dependencies [1dbb993]
|
|
3487
|
+
- Updated dependencies [2b5f509]
|
|
3488
|
+
- Updated dependencies [808f339]
|
|
3489
|
+
- Updated dependencies [6cf5999]
|
|
3490
|
+
- Updated dependencies [274e14a]
|
|
3491
|
+
- Updated dependencies [8c10f4f]
|
|
3492
|
+
- Updated dependencies [90dac98]
|
|
3493
|
+
- Updated dependencies [6096f20]
|
|
3494
|
+
- Updated dependencies [544aca2]
|
|
3495
|
+
- Updated dependencies [ea02938]
|
|
3496
|
+
- Updated dependencies [a14fb23]
|
|
3497
|
+
- Updated dependencies [ae98f1d]
|
|
3498
|
+
- Updated dependencies [f98eddf]
|
|
3499
|
+
- Updated dependencies [ce6bd99]
|
|
3500
|
+
- Updated dependencies [a5b08c9]
|
|
3501
|
+
- Updated dependencies [86982ac]
|
|
3502
|
+
- Updated dependencies [8c929e6]
|
|
3503
|
+
- Updated dependencies [cdefa2a]
|
|
3504
|
+
- Updated dependencies [2fc2a24]
|
|
3505
|
+
- Updated dependencies [0961d5e]
|
|
3506
|
+
- Updated dependencies [9d25b9b]
|
|
3507
|
+
- Updated dependencies [6276478]
|
|
3508
|
+
- Updated dependencies [5e67837]
|
|
3509
|
+
- Updated dependencies [ff14e29]
|
|
3510
|
+
- Updated dependencies [9b28151]
|
|
3511
|
+
- Updated dependencies [64563a9]
|
|
3512
|
+
- Updated dependencies [1a5003f]
|
|
3513
|
+
- Updated dependencies [09ab32b]
|
|
3514
|
+
- Updated dependencies [8acc51b]
|
|
3515
|
+
- Updated dependencies [ea9d17f]
|
|
3516
|
+
- Updated dependencies [45362a3]
|
|
3517
|
+
- Updated dependencies [2a943bf]
|
|
3518
|
+
- Updated dependencies [111fa4c]
|
|
3519
|
+
- Updated dependencies [93a689d]
|
|
3520
|
+
- Updated dependencies [4357a27]
|
|
3521
|
+
- Updated dependencies [e5f4343]
|
|
3522
|
+
- Updated dependencies [3261e64]
|
|
3523
|
+
- Updated dependencies [f5178a2]
|
|
3524
|
+
- Updated dependencies [2ad3671]
|
|
3525
|
+
- Updated dependencies [39bf246]
|
|
3526
|
+
- Updated dependencies [8740e86]
|
|
3527
|
+
- Updated dependencies [d22b37b]
|
|
3528
|
+
- Updated dependencies [8c0e550]
|
|
3529
|
+
- Updated dependencies [fde4caf]
|
|
3530
|
+
- Updated dependencies [caf0ed0]
|
|
3531
|
+
- Updated dependencies [1daf477]
|
|
3532
|
+
- Updated dependencies [3d614ea]
|
|
3533
|
+
- Updated dependencies [a7df45f]
|
|
3534
|
+
- Updated dependencies [6c2f3c5]
|
|
3535
|
+
- Updated dependencies [fb13e85]
|
|
3536
|
+
- Updated dependencies [c2d8659]
|
|
3537
|
+
- Updated dependencies [6516320]
|
|
3538
|
+
- Updated dependencies [98b1a7c]
|
|
3539
|
+
- Updated dependencies [e0f8202]
|
|
3540
|
+
- Updated dependencies [ac2d6f1]
|
|
3541
|
+
- Updated dependencies [9fbbb17]
|
|
3542
|
+
- Updated dependencies [c3a26cc]
|
|
3543
|
+
- Updated dependencies [a66e58e]
|
|
3544
|
+
- Updated dependencies [d89492c]
|
|
3545
|
+
- Updated dependencies [9a5f998]
|
|
3546
|
+
- Updated dependencies [1c5ee33]
|
|
3547
|
+
- Updated dependencies [9327397]
|
|
3548
|
+
- Updated dependencies [17cc3a3]
|
|
3549
|
+
- Updated dependencies [a9c5ea0]
|
|
3550
|
+
- Updated dependencies [02e6d36]
|
|
3551
|
+
- Updated dependencies [4758b33]
|
|
3552
|
+
- Updated dependencies [4758b33]
|
|
3553
|
+
- Updated dependencies [4758b33]
|
|
3554
|
+
- Updated dependencies [f905090]
|
|
3555
|
+
- Updated dependencies [3cd6c5e]
|
|
3556
|
+
- Updated dependencies [5bb855d]
|
|
3557
|
+
- Updated dependencies [9c78ebe]
|
|
3558
|
+
- Updated dependencies [12809a5]
|
|
3559
|
+
- Updated dependencies [9c78ebe]
|
|
3560
|
+
- Updated dependencies [12809a5]
|
|
3561
|
+
- Updated dependencies [7afc81d]
|
|
3562
|
+
- Updated dependencies [f9c06ef]
|
|
3563
|
+
- Updated dependencies [5ad3b88]
|
|
3564
|
+
- Updated dependencies [f9d772b]
|
|
3565
|
+
- Updated dependencies [bf14b64]
|
|
3566
|
+
- Updated dependencies [4345558]
|
|
3567
|
+
- Updated dependencies [97b6c21]
|
|
3568
|
+
- Updated dependencies [26ca2ad]
|
|
3569
|
+
- Updated dependencies [41ae65b]
|
|
3570
|
+
- Updated dependencies [baac95a]
|
|
3571
|
+
- Updated dependencies [13220af]
|
|
3572
|
+
- Updated dependencies [29b45f6]
|
|
3573
|
+
- Updated dependencies [39b8d51]
|
|
3574
|
+
- Updated dependencies [17b323e]
|
|
3575
|
+
- Updated dependencies [b956e69]
|
|
3576
|
+
- Updated dependencies [b32e7de]
|
|
3577
|
+
- Updated dependencies [ff94a12]
|
|
3578
|
+
- Updated dependencies [33e58d8]
|
|
3579
|
+
- Updated dependencies [256b4c9]
|
|
3580
|
+
- Updated dependencies [97672ba]
|
|
3581
|
+
- Updated dependencies [c5ec15c]
|
|
3582
|
+
- Updated dependencies [fec3b1a]
|
|
3583
|
+
- Updated dependencies [b8e0941]
|
|
3584
|
+
- Updated dependencies [0c50f18]
|
|
3585
|
+
- Updated dependencies [1dae95a]
|
|
3586
|
+
- Updated dependencies [e32dae1]
|
|
3587
|
+
- Updated dependencies [d0c0c7f]
|
|
3588
|
+
- Updated dependencies [30b11ad]
|
|
3589
|
+
- Updated dependencies [4aebea0]
|
|
3590
|
+
- Updated dependencies [f976774]
|
|
3591
|
+
- Updated dependencies [3d6badf]
|
|
3592
|
+
- Updated dependencies [25cb364]
|
|
3593
|
+
- Updated dependencies [a60539b]
|
|
3594
|
+
- Updated dependencies [de1b879]
|
|
3595
|
+
- Updated dependencies [c6678b1]
|
|
3596
|
+
- Updated dependencies [0638322]
|
|
3597
|
+
- Updated dependencies [e3782d2]
|
|
3598
|
+
- Updated dependencies [db0beb2]
|
|
3599
|
+
- Updated dependencies [997ce38]
|
|
3600
|
+
- Updated dependencies [ae0b9d3]
|
|
3601
|
+
- Updated dependencies [ad1785c]
|
|
3602
|
+
- Updated dependencies [3b469c8]
|
|
3603
|
+
- Updated dependencies [990a2d6]
|
|
3604
|
+
- Updated dependencies [990a2d6]
|
|
3605
|
+
- Updated dependencies [1c84036]
|
|
3606
|
+
- Updated dependencies [1c84036]
|
|
3607
|
+
- Updated dependencies [6650259]
|
|
3608
|
+
- Updated dependencies [4f8b7f8]
|
|
3609
|
+
- Updated dependencies [9e6619f]
|
|
3610
|
+
- Updated dependencies [f6ae5e2]
|
|
3611
|
+
- Updated dependencies [eb97ce6]
|
|
3612
|
+
- Updated dependencies [7343376]
|
|
3613
|
+
- Updated dependencies [b2683a2]
|
|
3614
|
+
- Updated dependencies [dded788]
|
|
3615
|
+
- Updated dependencies [b45d463]
|
|
3616
|
+
- Updated dependencies [54a7830]
|
|
3617
|
+
- Updated dependencies [f3135a4]
|
|
3618
|
+
- Updated dependencies [b5696d3]
|
|
3619
|
+
- Updated dependencies [3f9d926]
|
|
3620
|
+
- Updated dependencies [e978ed5]
|
|
3621
|
+
- Updated dependencies [6a7f24e]
|
|
3622
|
+
- Updated dependencies [b3c96d6]
|
|
3623
|
+
- Updated dependencies [8d0ca91]
|
|
3624
|
+
- Updated dependencies [c30c8dd]
|
|
3625
|
+
- Updated dependencies [244d516]
|
|
3626
|
+
- Updated dependencies [deca847]
|
|
3627
|
+
- Updated dependencies [ac15833]
|
|
3628
|
+
- Updated dependencies [ac15833]
|
|
3629
|
+
- Updated dependencies [e2dffc9]
|
|
3630
|
+
- Updated dependencies [328abeb]
|
|
3631
|
+
- Updated dependencies [dd0d78f]
|
|
3632
|
+
- Updated dependencies [24d3e65]
|
|
3633
|
+
- Updated dependencies [95a7c8d]
|
|
3634
|
+
- Updated dependencies [e227156]
|
|
3635
|
+
- Updated dependencies [cc4e476]
|
|
3636
|
+
- Updated dependencies [92970c4]
|
|
3637
|
+
- Updated dependencies [d570eaa]
|
|
3638
|
+
- Updated dependencies [4b742f4]
|
|
3639
|
+
- Updated dependencies [42687ba]
|
|
3640
|
+
- Updated dependencies [6cd8f66]
|
|
3641
|
+
- Updated dependencies [24a0f14]
|
|
3642
|
+
- Updated dependencies [797a30f]
|
|
3643
|
+
- Updated dependencies [b4075c0]
|
|
3644
|
+
- Updated dependencies [9b85600]
|
|
3645
|
+
- Updated dependencies [e327c89]
|
|
3646
|
+
- Updated dependencies [2eaf5be]
|
|
3647
|
+
- Updated dependencies [bf7ab35]
|
|
3648
|
+
- Updated dependencies [99878d8]
|
|
3649
|
+
- Updated dependencies [3c13675]
|
|
3650
|
+
- Updated dependencies [63ab761]
|
|
3651
|
+
- Updated dependencies [0eb9f36]
|
|
3652
|
+
- Updated dependencies [ae582b7]
|
|
3653
|
+
- Updated dependencies [51c2949]
|
|
3654
|
+
- Updated dependencies [a4b017e]
|
|
3655
|
+
- Updated dependencies [8732846]
|
|
3656
|
+
- Updated dependencies [559a2e2]
|
|
3657
|
+
- Updated dependencies [db11afd]
|
|
3658
|
+
- Updated dependencies [154075a]
|
|
3659
|
+
- Updated dependencies [582edef]
|
|
3660
|
+
- Updated dependencies [19f484f]
|
|
3661
|
+
- Updated dependencies [0a78a20]
|
|
3662
|
+
- Updated dependencies [615346d]
|
|
3663
|
+
- Updated dependencies [75dcc81]
|
|
3664
|
+
- Updated dependencies [55a12a8]
|
|
3665
|
+
- Updated dependencies [edfcf5a]
|
|
3666
|
+
- Updated dependencies [0a3e540]
|
|
3667
|
+
- Updated dependencies [c021b35]
|
|
3668
|
+
- Updated dependencies [f61dab1]
|
|
3669
|
+
- Updated dependencies [b0a05dd]
|
|
3670
|
+
- Updated dependencies [dd5ff19]
|
|
3671
|
+
- Updated dependencies [54997ff]
|
|
3672
|
+
- Updated dependencies [81f8498]
|
|
3673
|
+
- Updated dependencies [a782fa7]
|
|
3674
|
+
- Updated dependencies [1d6a23d]
|
|
3675
|
+
- Updated dependencies [0645133]
|
|
3676
|
+
- Updated dependencies [76e9df0]
|
|
3677
|
+
- Updated dependencies [0ecaa7d]
|
|
3678
|
+
- Updated dependencies [b24f93a]
|
|
3679
|
+
- Updated dependencies [6158e4c]
|
|
3680
|
+
- Updated dependencies [cff8641]
|
|
3681
|
+
- Updated dependencies [c27b575]
|
|
3682
|
+
- Updated dependencies [84b275c]
|
|
3683
|
+
- Updated dependencies [0e6e76b]
|
|
3684
|
+
- Updated dependencies [bf43afa]
|
|
3685
|
+
- Updated dependencies [385ebc5]
|
|
3686
|
+
- Updated dependencies [3a0e7ab]
|
|
3687
|
+
- Updated dependencies [858eafb]
|
|
3688
|
+
- Updated dependencies [a8c5509]
|
|
3689
|
+
- Updated dependencies [c2a8d23]
|
|
3690
|
+
- Updated dependencies [0ffc423]
|
|
3691
|
+
- Updated dependencies [3cc4fe5]
|
|
3692
|
+
- Updated dependencies [cd5b19a]
|
|
3693
|
+
- Updated dependencies [cd5b19a]
|
|
3694
|
+
- Updated dependencies [17dc167]
|
|
3695
|
+
- Updated dependencies [20d23be]
|
|
3696
|
+
- Updated dependencies [20d23be]
|
|
3697
|
+
- Updated dependencies [2e3da72]
|
|
3698
|
+
- Updated dependencies [1263e40]
|
|
3699
|
+
- Updated dependencies [e6bc087]
|
|
3700
|
+
- Updated dependencies [9419df1]
|
|
3701
|
+
- Updated dependencies [8bab157]
|
|
3702
|
+
- Updated dependencies [a7557a7]
|
|
3703
|
+
- Updated dependencies [7d074ba]
|
|
3704
|
+
- Updated dependencies [6158e4c]
|
|
3705
|
+
- Updated dependencies [6158e4c]
|
|
3706
|
+
- Updated dependencies [52aad5c]
|
|
3707
|
+
- Updated dependencies [18d1a0a]
|
|
3708
|
+
- Updated dependencies [7fed09d]
|
|
3709
|
+
- Updated dependencies [c1763e5]
|
|
3710
|
+
- Updated dependencies [58da8ae]
|
|
3711
|
+
- Updated dependencies [a8b9889]
|
|
3712
|
+
- Updated dependencies [138ad45]
|
|
3713
|
+
- Updated dependencies [138ad45]
|
|
3714
|
+
- Updated dependencies [5262f7d]
|
|
3715
|
+
- Updated dependencies [6aa029b]
|
|
3716
|
+
- Updated dependencies [2124d04]
|
|
3717
|
+
- Updated dependencies [be52115]
|
|
3718
|
+
- Updated dependencies [770cc5b]
|
|
3719
|
+
- Updated dependencies [1a88ce2]
|
|
3720
|
+
- Updated dependencies [a1a44d6]
|
|
3721
|
+
- Updated dependencies [e0a9c67]
|
|
3722
|
+
- Updated dependencies [5638529]
|
|
3723
|
+
- Updated dependencies [5638529]
|
|
3724
|
+
- Updated dependencies [d0fba91]
|
|
3725
|
+
- Updated dependencies [c476be0]
|
|
3726
|
+
- Updated dependencies [c82ff39]
|
|
3727
|
+
- Updated dependencies [6c3da53]
|
|
3728
|
+
- Updated dependencies [31987bd]
|
|
3729
|
+
- Updated dependencies [3c3ce15]
|
|
3730
|
+
- Updated dependencies [063119f]
|
|
3731
|
+
- Updated dependencies [e100589]
|
|
3732
|
+
- Updated dependencies [304f611]
|
|
3733
|
+
- Updated dependencies [e46ee77]
|
|
3734
|
+
- Updated dependencies [f9c8c4e]
|
|
3735
|
+
- Updated dependencies [6e9c8d2]
|
|
3736
|
+
- Updated dependencies [9547063]
|
|
3737
|
+
- Updated dependencies [3f6efd6]
|
|
3738
|
+
- Updated dependencies [52c95a1]
|
|
3739
|
+
- Updated dependencies [55d18c6]
|
|
3740
|
+
- Updated dependencies [c4ab6d0]
|
|
3741
|
+
- Updated dependencies [0e9058b]
|
|
3742
|
+
- Updated dependencies [c4ab6d0]
|
|
3743
|
+
- Updated dependencies [5988b6b]
|
|
3744
|
+
- Updated dependencies [6158e4c]
|
|
3745
|
+
- Updated dependencies [00ccdf7]
|
|
3746
|
+
- Updated dependencies [9d9ed54]
|
|
3747
|
+
- Updated dependencies [6007dd4]
|
|
3748
|
+
- Updated dependencies [4a1adb7]
|
|
3749
|
+
- Updated dependencies [4a1adb7]
|
|
3750
|
+
- Updated dependencies [4a1adb7]
|
|
3751
|
+
- Updated dependencies [185b7a0]
|
|
3752
|
+
- Updated dependencies [50c73fe]
|
|
3753
|
+
- Updated dependencies [ca3de72]
|
|
3754
|
+
- Updated dependencies [83e3f83]
|
|
3755
|
+
- Updated dependencies [401611b]
|
|
3756
|
+
- Updated dependencies [2c0ddf2]
|
|
3757
|
+
- Updated dependencies [4abc0aa]
|
|
3758
|
+
- Updated dependencies [f560ded]
|
|
3759
|
+
- Updated dependencies [2b188fa]
|
|
3760
|
+
- Updated dependencies [b654d4e]
|
|
3761
|
+
- Updated dependencies [f68e0a0]
|
|
3762
|
+
- Updated dependencies [aea682a]
|
|
3763
|
+
- Updated dependencies [fcdc8ec]
|
|
3764
|
+
- Updated dependencies [2d576e4]
|
|
3765
|
+
- Updated dependencies [8366acc]
|
|
3766
|
+
- Updated dependencies [95e58a3]
|
|
3767
|
+
- Updated dependencies [9d7419b]
|
|
3768
|
+
- Updated dependencies [fc7db05]
|
|
3769
|
+
- Updated dependencies [9ed8d0f]
|
|
3770
|
+
- Updated dependencies [c73cdb5]
|
|
3771
|
+
- Updated dependencies [6f5719e]
|
|
3772
|
+
- Updated dependencies [d0097af]
|
|
3773
|
+
- Updated dependencies [432882b]
|
|
3774
|
+
- Updated dependencies [64dae8e]
|
|
3775
|
+
- Updated dependencies [b06e374]
|
|
3776
|
+
- Updated dependencies [06a8af5]
|
|
3777
|
+
- Updated dependencies [6a91586]
|
|
3778
|
+
- Updated dependencies [a04d7c6]
|
|
3779
|
+
- Updated dependencies [5ccc500]
|
|
3780
|
+
- Updated dependencies [f3c2bb0]
|
|
3781
|
+
- Updated dependencies [978507b]
|
|
3782
|
+
- Updated dependencies [778138e]
|
|
3783
|
+
- Updated dependencies [9801765]
|
|
3784
|
+
- Updated dependencies [9cebfca]
|
|
3785
|
+
- Updated dependencies [460575f]
|
|
3786
|
+
- Updated dependencies [d796c8d]
|
|
3787
|
+
- Updated dependencies [594704f]
|
|
3788
|
+
- Updated dependencies [d3995fe]
|
|
3789
|
+
- Updated dependencies [1b1d772]
|
|
3790
|
+
- Updated dependencies [d88e20f]
|
|
3791
|
+
- Updated dependencies [2d7304d]
|
|
3792
|
+
- Updated dependencies [636b236]
|
|
3793
|
+
- Updated dependencies [4172589]
|
|
3794
|
+
- Updated dependencies [d6d8fb9]
|
|
3795
|
+
- Updated dependencies [64d624d]
|
|
3796
|
+
- Updated dependencies [053fdc8]
|
|
3797
|
+
- Updated dependencies [41b7ce3]
|
|
3798
|
+
- Updated dependencies [ae476b8]
|
|
3799
|
+
- Updated dependencies [39f4309]
|
|
3800
|
+
- Updated dependencies [95bad12]
|
|
3801
|
+
- Updated dependencies [d2fb6ef]
|
|
3802
|
+
- Updated dependencies [7cd3987]
|
|
3803
|
+
- Updated dependencies [ee3b878]
|
|
3804
|
+
- Updated dependencies [e304a4e]
|
|
3805
|
+
- Updated dependencies [fda49e5]
|
|
3806
|
+
- Updated dependencies [490d9a9]
|
|
3807
|
+
- Updated dependencies [fc62bb4]
|
|
3808
|
+
- Updated dependencies [41df893]
|
|
3809
|
+
- Updated dependencies [0cba1b7]
|
|
3810
|
+
- Updated dependencies [00f3eb5]
|
|
3811
|
+
- Updated dependencies [1ec291c]
|
|
3812
|
+
- Updated dependencies [453dbaa]
|
|
3813
|
+
- Updated dependencies [95f8704]
|
|
3814
|
+
- Updated dependencies [f8cdbf2]
|
|
3815
|
+
- Updated dependencies [69a2163]
|
|
3816
|
+
- Updated dependencies [24e027e]
|
|
3817
|
+
- Updated dependencies [2c3cd1b]
|
|
3818
|
+
- Updated dependencies [e176053]
|
|
3819
|
+
- Updated dependencies [e30ed15]
|
|
3820
|
+
- Updated dependencies [90665e0]
|
|
3821
|
+
- Updated dependencies [8d3a529]
|
|
3822
|
+
- Updated dependencies [5ac2e2c]
|
|
3823
|
+
- Updated dependencies [194fae1]
|
|
3824
|
+
- Updated dependencies [7e19d03]
|
|
3825
|
+
- Updated dependencies [b08b7eb]
|
|
3826
|
+
- Updated dependencies [1e946c9]
|
|
3827
|
+
- Updated dependencies [546ddf7]
|
|
3828
|
+
- Updated dependencies [864154e]
|
|
3829
|
+
- Updated dependencies [b023625]
|
|
3830
|
+
- Updated dependencies [75bd83d]
|
|
3831
|
+
- Updated dependencies [44d075b]
|
|
3832
|
+
- Updated dependencies [40c479a]
|
|
3833
|
+
- Updated dependencies [971d387]
|
|
3834
|
+
- Updated dependencies [ee851c3]
|
|
3835
|
+
- Updated dependencies [6414dfd]
|
|
3836
|
+
- Updated dependencies [a8d5c71]
|
|
3837
|
+
- Updated dependencies [905b21f]
|
|
3838
|
+
- Updated dependencies [88e9109]
|
|
3839
|
+
- Updated dependencies [2c45966]
|
|
3840
|
+
- Updated dependencies [db3a600]
|
|
3841
|
+
- Updated dependencies [3a3db76]
|
|
3842
|
+
- Updated dependencies [0d723a3]
|
|
3843
|
+
- Updated dependencies [0c95d3d]
|
|
3844
|
+
- Updated dependencies [3e4fa2c]
|
|
3845
|
+
- Updated dependencies [b5b928a]
|
|
3846
|
+
- Updated dependencies [6fd2cf7]
|
|
3847
|
+
- Updated dependencies [5fa06c4]
|
|
3848
|
+
- Updated dependencies [52a43de]
|
|
3849
|
+
- Updated dependencies [195052f]
|
|
3850
|
+
- Updated dependencies [e4559d1]
|
|
3851
|
+
- Updated dependencies [2c71482]
|
|
3852
|
+
- Updated dependencies [129bcc5]
|
|
3853
|
+
- Updated dependencies [a26b9e4]
|
|
3854
|
+
- Updated dependencies [5ef9c4f]
|
|
3855
|
+
- Updated dependencies [46f0bb4]
|
|
3856
|
+
- Updated dependencies [06b82b8]
|
|
3857
|
+
- Updated dependencies [8ec11e1]
|
|
3858
|
+
- Updated dependencies [6f81384]
|
|
3859
|
+
- Updated dependencies [22ba927]
|
|
3860
|
+
- Updated dependencies [8631c32]
|
|
3861
|
+
- Updated dependencies [f8c70f4]
|
|
3862
|
+
- Updated dependencies [5d3a2d1]
|
|
3863
|
+
- Updated dependencies [8f1d995]
|
|
3864
|
+
- Updated dependencies [b362c1b]
|
|
3865
|
+
- Updated dependencies [f9c34df]
|
|
3866
|
+
- Updated dependencies [dddb942]
|
|
3867
|
+
- Updated dependencies [00c665e]
|
|
3868
|
+
- Updated dependencies [29754cf]
|
|
3869
|
+
- Updated dependencies [d7de534]
|
|
3870
|
+
- Updated dependencies [3c2b6f7]
|
|
3871
|
+
- Updated dependencies [6e88630]
|
|
3872
|
+
- Updated dependencies [b84dc18]
|
|
3873
|
+
- Updated dependencies [ac8abb0]
|
|
3874
|
+
- Updated dependencies [9d86e1d]
|
|
3875
|
+
- Updated dependencies [3a5817f]
|
|
3876
|
+
- Updated dependencies [99a3c2d]
|
|
3877
|
+
- Updated dependencies [5961030]
|
|
3878
|
+
- Updated dependencies [f24de8b]
|
|
3879
|
+
- Updated dependencies [c8ea8af]
|
|
3880
|
+
- Updated dependencies [9602dc8]
|
|
3881
|
+
- Updated dependencies [3190414]
|
|
3882
|
+
- Updated dependencies [4e480f5]
|
|
3883
|
+
- Updated dependencies [38a123c]
|
|
3884
|
+
- Updated dependencies [299102e]
|
|
3885
|
+
- Updated dependencies [30c73cd]
|
|
3886
|
+
- Updated dependencies [830ed58]
|
|
3887
|
+
- Updated dependencies [d7acad6]
|
|
3888
|
+
- Updated dependencies [45a9aeb]
|
|
3889
|
+
- Updated dependencies [713db46]
|
|
3890
|
+
- Updated dependencies [c71e14d]
|
|
3891
|
+
- Updated dependencies [bf3a03c]
|
|
3892
|
+
- Updated dependencies [cb55718]
|
|
3893
|
+
- Updated dependencies [748494b]
|
|
3894
|
+
- Updated dependencies [5967be0]
|
|
3895
|
+
- Updated dependencies [831be72]
|
|
3896
|
+
- Updated dependencies [29cb85b]
|
|
3897
|
+
- Updated dependencies [3e028c8]
|
|
3898
|
+
- Updated dependencies [d0889e2]
|
|
3899
|
+
- Updated dependencies [ce503e5]
|
|
3900
|
+
- Updated dependencies [f20dcf0]
|
|
3901
|
+
- Updated dependencies [12402a9]
|
|
3902
|
+
- Updated dependencies [aff3d7a]
|
|
3903
|
+
- Updated dependencies [4ca30d0]
|
|
3904
|
+
- Updated dependencies [7a5da14]
|
|
3905
|
+
- Updated dependencies [fff9645]
|
|
3906
|
+
- Updated dependencies [9c3b7ce]
|
|
3907
|
+
- Updated dependencies [2c1c967]
|
|
3908
|
+
- Updated dependencies [9486ac6]
|
|
3909
|
+
- Updated dependencies [9486ac6]
|
|
3910
|
+
- Updated dependencies [4d5f9b4]
|
|
3911
|
+
- Updated dependencies [d6ceb8d]
|
|
3912
|
+
- Updated dependencies [dc4365c]
|
|
3913
|
+
- Updated dependencies [e321d52]
|
|
3914
|
+
- Updated dependencies [969ba84]
|
|
3915
|
+
- Updated dependencies [98188c2]
|
|
3916
|
+
- Updated dependencies [4c68077]
|
|
3917
|
+
- Updated dependencies [7977ff9]
|
|
3918
|
+
- Updated dependencies [3beef6d]
|
|
3919
|
+
- Updated dependencies [06b8c42]
|
|
3920
|
+
- Updated dependencies [46b9bc9]
|
|
3921
|
+
- Updated dependencies [f46bd39]
|
|
3922
|
+
- Updated dependencies [b98352a]
|
|
3923
|
+
- Updated dependencies [b76ca67]
|
|
3924
|
+
- Updated dependencies [45ac2cb]
|
|
3925
|
+
- Updated dependencies [b97790a]
|
|
3926
|
+
- Updated dependencies [dbd5194]
|
|
3927
|
+
- Updated dependencies [7c9b044]
|
|
3928
|
+
- Updated dependencies [e552c31]
|
|
3929
|
+
- Updated dependencies [d47de51]
|
|
3930
|
+
- Updated dependencies [3fe6463]
|
|
3931
|
+
- Updated dependencies [b392674]
|
|
3932
|
+
- Updated dependencies [4f3a1e2]
|
|
3933
|
+
- Updated dependencies [31ab372]
|
|
3934
|
+
- Updated dependencies [846889b]
|
|
3935
|
+
- Updated dependencies [2acd8e1]
|
|
3936
|
+
- Updated dependencies [7b90231]
|
|
3937
|
+
- Updated dependencies [26896c6]
|
|
3938
|
+
- Updated dependencies [67fc3b0]
|
|
3939
|
+
- Updated dependencies [8579e34]
|
|
3940
|
+
- Updated dependencies [d57db5d]
|
|
3941
|
+
- Updated dependencies [33a3b3c]
|
|
3942
|
+
- Updated dependencies [b87f15b]
|
|
3943
|
+
- Updated dependencies [045d20b]
|
|
3944
|
+
- Updated dependencies [a2d2515]
|
|
3945
|
+
- Updated dependencies [c18d099]
|
|
3946
|
+
- Updated dependencies [0caacca]
|
|
3947
|
+
- Updated dependencies [adb2a86]
|
|
3948
|
+
- Updated dependencies [03380aa]
|
|
3949
|
+
- Updated dependencies [4562ea5]
|
|
3950
|
+
- Updated dependencies [3619792]
|
|
3951
|
+
- Updated dependencies [3561bd2]
|
|
3952
|
+
- Updated dependencies [bf97b98]
|
|
3953
|
+
- Updated dependencies [320374d]
|
|
3954
|
+
- Updated dependencies [b0d308d]
|
|
3955
|
+
- Updated dependencies [b458300]
|
|
3956
|
+
- Updated dependencies [40f34b4]
|
|
3957
|
+
- Updated dependencies [bd0376d]
|
|
3958
|
+
- Updated dependencies [8063bcb]
|
|
3959
|
+
- Updated dependencies [b74a859]
|
|
3960
|
+
- Updated dependencies [d4493fd]
|
|
3961
|
+
- Updated dependencies [240b80f]
|
|
3962
|
+
- Updated dependencies [77cb489]
|
|
3963
|
+
- Updated dependencies [bfaa158]
|
|
3964
|
+
- Updated dependencies [777e5c6]
|
|
3965
|
+
- Updated dependencies [0c386dd]
|
|
3966
|
+
- Updated dependencies [39d69ad]
|
|
3967
|
+
- Updated dependencies [9e37d9b]
|
|
3968
|
+
- Updated dependencies [5ad86dd]
|
|
3969
|
+
- Updated dependencies [16a725f]
|
|
3970
|
+
- Updated dependencies [4dfdcc3]
|
|
3971
|
+
- Updated dependencies [6a449fc]
|
|
3972
|
+
- Updated dependencies [446d93d]
|
|
3973
|
+
- Updated dependencies [ecd9cb2]
|
|
3974
|
+
- Updated dependencies [f08bcd9]
|
|
3975
|
+
- Updated dependencies [98d4108]
|
|
3976
|
+
- Updated dependencies [0e3b3be]
|
|
3977
|
+
- Updated dependencies [a29ae2d]
|
|
3978
|
+
- Updated dependencies [220c18d]
|
|
3979
|
+
- Updated dependencies [00d3f09]
|
|
3980
|
+
- Updated dependencies [4388f71]
|
|
3981
|
+
- Updated dependencies [0b1ac58]
|
|
3982
|
+
- Updated dependencies [c93b4d5]
|
|
3983
|
+
- Updated dependencies [c1fe272]
|
|
3984
|
+
- Updated dependencies [3cab570]
|
|
3985
|
+
- Updated dependencies [8ad218d]
|
|
3986
|
+
- Updated dependencies [3e41187]
|
|
3987
|
+
- Updated dependencies [5f78953]
|
|
3988
|
+
- Updated dependencies [639114c]
|
|
3989
|
+
- Updated dependencies [639114c]
|
|
3990
|
+
- Updated dependencies [1490691]
|
|
3991
|
+
- Updated dependencies [e8e4c4d]
|
|
3992
|
+
- Updated dependencies [1f31d3a]
|
|
3993
|
+
- Updated dependencies [d1842ab]
|
|
3994
|
+
- Updated dependencies [78ca238]
|
|
3995
|
+
- Updated dependencies [d8ec8d6]
|
|
3996
|
+
- Updated dependencies [351eb31]
|
|
3997
|
+
- Updated dependencies [866cd1d]
|
|
3998
|
+
- Updated dependencies [20c04b2]
|
|
3999
|
+
- Updated dependencies [01c9023]
|
|
4000
|
+
- Updated dependencies [48c19bd]
|
|
4001
|
+
- Updated dependencies [a6d8b8d]
|
|
4002
|
+
- Updated dependencies [4b5bb95]
|
|
4003
|
+
- Updated dependencies [b652514]
|
|
4004
|
+
- Updated dependencies [adbda1b]
|
|
4005
|
+
- Updated dependencies [adbda1b]
|
|
4006
|
+
- Updated dependencies [8952395]
|
|
4007
|
+
- Updated dependencies [e2b3826]
|
|
4008
|
+
- Updated dependencies [0348bc9]
|
|
4009
|
+
- Updated dependencies [e8c553b]
|
|
4010
|
+
- Updated dependencies [2e32ed4]
|
|
4011
|
+
- Updated dependencies [554e647]
|
|
4012
|
+
- Updated dependencies [3ed3eec]
|
|
4013
|
+
- Updated dependencies [7c3df8f]
|
|
4014
|
+
- Updated dependencies [a4514e8]
|
|
4015
|
+
- Updated dependencies [db3896c]
|
|
4016
|
+
- Updated dependencies [b9f5ff1]
|
|
4017
|
+
- Updated dependencies [e75f4c9]
|
|
4018
|
+
- Updated dependencies [19f1639]
|
|
4019
|
+
- Updated dependencies [4704aa4]
|
|
4020
|
+
- Updated dependencies [47547d0]
|
|
4021
|
+
- Updated dependencies [1bee5d0]
|
|
4022
|
+
- Updated dependencies [858cd72]
|
|
4023
|
+
- Updated dependencies [cfc9b6d]
|
|
4024
|
+
- Updated dependencies [554f2b6]
|
|
4025
|
+
- Updated dependencies [72f55c9]
|
|
4026
|
+
- Updated dependencies [26e06d7]
|
|
4027
|
+
- Updated dependencies [669d71b]
|
|
4028
|
+
- Updated dependencies [ed27d7c]
|
|
4029
|
+
- Updated dependencies [52c8cf7]
|
|
4030
|
+
- Updated dependencies [2ceb43a]
|
|
4031
|
+
- Updated dependencies [7cdd2b9]
|
|
4032
|
+
- Updated dependencies [52c8cf7]
|
|
4033
|
+
- Updated dependencies [3399704]
|
|
4034
|
+
- Updated dependencies [71a4a53]
|
|
4035
|
+
- Updated dependencies [7bf244b]
|
|
4036
|
+
- Updated dependencies [f0bb9fa]
|
|
4037
|
+
- Updated dependencies [81a2eb1]
|
|
4038
|
+
- Updated dependencies [caa0cd3]
|
|
4039
|
+
- Updated dependencies [caa0cd3]
|
|
4040
|
+
- Updated dependencies [20cb8db]
|
|
4041
|
+
- Updated dependencies [25c7d58]
|
|
4042
|
+
- Updated dependencies [00d2fa6]
|
|
4043
|
+
- Updated dependencies [77b2a18]
|
|
4044
|
+
- Updated dependencies [c6198c2]
|
|
4045
|
+
- Updated dependencies [721d1e0]
|
|
4046
|
+
- Updated dependencies [c2f0f48]
|
|
4047
|
+
- Updated dependencies [1237ae4]
|
|
4048
|
+
- Updated dependencies [2f61238]
|
|
4049
|
+
- Updated dependencies [51eb515]
|
|
4050
|
+
- Updated dependencies [c354ce5]
|
|
4051
|
+
- Updated dependencies [8fe8e5c]
|
|
4052
|
+
- Updated dependencies [feac439]
|
|
4053
|
+
- Updated dependencies [9ae871d]
|
|
4054
|
+
- Updated dependencies [efbd566]
|
|
4055
|
+
- Updated dependencies [2a5bf45]
|
|
4056
|
+
- Updated dependencies [9587fc9]
|
|
4057
|
+
- Updated dependencies [e62c44e]
|
|
4058
|
+
- Updated dependencies [daf9d57]
|
|
4059
|
+
- Updated dependencies [23b9958]
|
|
4060
|
+
- Updated dependencies [c15d7ec]
|
|
4061
|
+
- Updated dependencies [5d0876c]
|
|
4062
|
+
- Updated dependencies [f7ace0a]
|
|
4063
|
+
- Updated dependencies [b041b9c]
|
|
4064
|
+
- Updated dependencies [ce2aaef]
|
|
4065
|
+
- Updated dependencies [544ecba]
|
|
4066
|
+
- Updated dependencies [2ce2612]
|
|
4067
|
+
- Updated dependencies [bc640ec]
|
|
4068
|
+
- Updated dependencies [1e215c4]
|
|
4069
|
+
- Updated dependencies [da6e191]
|
|
4070
|
+
- Updated dependencies [3e377c9]
|
|
4071
|
+
- Updated dependencies [a3eb5d0]
|
|
4072
|
+
- Updated dependencies [4ce14f1]
|
|
4073
|
+
- Updated dependencies [aef97e5]
|
|
4074
|
+
- Updated dependencies [2af1fa7]
|
|
4075
|
+
- Updated dependencies [c14d3a0]
|
|
4076
|
+
- Updated dependencies [a137d0c]
|
|
4077
|
+
- Updated dependencies [caf477f]
|
|
4078
|
+
- Updated dependencies [f6375da]
|
|
4079
|
+
- Updated dependencies [967e5d8]
|
|
4080
|
+
- Updated dependencies [c907a9c]
|
|
4081
|
+
- Updated dependencies [a4611b3]
|
|
4082
|
+
- Updated dependencies [20316ba]
|
|
4083
|
+
- Updated dependencies [d3499b3]
|
|
4084
|
+
- Updated dependencies [91f9276]
|
|
4085
|
+
- Updated dependencies [309c75e]
|
|
4086
|
+
- Updated dependencies [c9f9bae]
|
|
4087
|
+
- Updated dependencies [18897a4]
|
|
4088
|
+
- Updated dependencies [8b7ea39]
|
|
4089
|
+
- Updated dependencies [a915064]
|
|
4090
|
+
- Updated dependencies [dcbf0b2]
|
|
4091
|
+
- Updated dependencies [52cac38]
|
|
4092
|
+
- Updated dependencies [93fea2e]
|
|
4093
|
+
- Updated dependencies [1422a92]
|
|
4094
|
+
- Updated dependencies [d05fe17]
|
|
4095
|
+
- Updated dependencies [a480f79]
|
|
4096
|
+
- Updated dependencies [f08d1a8]
|
|
4097
|
+
- Updated dependencies [64a252d]
|
|
4098
|
+
- Updated dependencies [786bc91]
|
|
4099
|
+
- Updated dependencies [75fca96]
|
|
4100
|
+
- Updated dependencies [7ca6ddd]
|
|
4101
|
+
- Updated dependencies [f1cd290]
|
|
4102
|
+
- Updated dependencies [5a41ce7]
|
|
4103
|
+
- Updated dependencies [8d50bc2]
|
|
4104
|
+
- Updated dependencies [604476d]
|
|
4105
|
+
- Updated dependencies [d1bebb0]
|
|
4106
|
+
- Updated dependencies [95bf128]
|
|
4107
|
+
- Updated dependencies [335abea]
|
|
4108
|
+
- Updated dependencies [edea22a]
|
|
4109
|
+
- Updated dependencies [0f5cadf]
|
|
4110
|
+
- Updated dependencies [4f9f1ee]
|
|
4111
|
+
- Updated dependencies [66e8b2a]
|
|
4112
|
+
- Updated dependencies [aa083cd]
|
|
4113
|
+
- Updated dependencies [12b5992]
|
|
4114
|
+
- Updated dependencies [b93e245]
|
|
4115
|
+
- Updated dependencies [c842594]
|
|
4116
|
+
- Updated dependencies [290de37]
|
|
4117
|
+
- Updated dependencies [e1c27e4]
|
|
4118
|
+
- Updated dependencies [8c8da45]
|
|
4119
|
+
- Updated dependencies [8cd8eb5]
|
|
4120
|
+
- Updated dependencies [f52a9d7]
|
|
4121
|
+
- Updated dependencies [cf1d29e]
|
|
4122
|
+
- Updated dependencies [1bd79c8]
|
|
4123
|
+
- Updated dependencies [af9e957]
|
|
4124
|
+
- Updated dependencies [b1030c7]
|
|
4125
|
+
- Updated dependencies [c974edf]
|
|
4126
|
+
- Updated dependencies [ad852b6]
|
|
4127
|
+
- Updated dependencies [6778809]
|
|
4128
|
+
- Updated dependencies [3a3dd28]
|
|
4129
|
+
- Updated dependencies [7fb22a1]
|
|
4130
|
+
- Updated dependencies [ad66d79]
|
|
4131
|
+
- Updated dependencies [0758bd8]
|
|
4132
|
+
- Updated dependencies [ee4d19f]
|
|
4133
|
+
- Updated dependencies [496d31d]
|
|
4134
|
+
- Updated dependencies [7ed9808]
|
|
4135
|
+
- Updated dependencies [0ea7054]
|
|
4136
|
+
- Updated dependencies [ac0e39a]
|
|
4137
|
+
- Updated dependencies [9a853f2]
|
|
4138
|
+
- Updated dependencies [cb847fd]
|
|
4139
|
+
- Updated dependencies [ee70287]
|
|
4140
|
+
- Updated dependencies [3e98e13]
|
|
4141
|
+
- Updated dependencies [a695f50]
|
|
4142
|
+
- Updated dependencies [fc32921]
|
|
4143
|
+
- Updated dependencies [4eaa835]
|
|
4144
|
+
- Updated dependencies [8f9d87a]
|
|
4145
|
+
- Updated dependencies [b1777ae]
|
|
4146
|
+
- Updated dependencies [24845c4]
|
|
4147
|
+
- Updated dependencies [6f864cf]
|
|
4148
|
+
- Updated dependencies [24d1edd]
|
|
4149
|
+
- Updated dependencies [645087c]
|
|
4150
|
+
- Updated dependencies [33f4a19]
|
|
4151
|
+
- Updated dependencies [6e9a3d4]
|
|
4152
|
+
- Updated dependencies [4a292d2]
|
|
4153
|
+
- Updated dependencies [5323168]
|
|
4154
|
+
- Updated dependencies [841dd2b]
|
|
4155
|
+
- Updated dependencies [3014fc0]
|
|
4156
|
+
- Updated dependencies [dacb402]
|
|
4157
|
+
- Updated dependencies [846cec0]
|
|
4158
|
+
- Updated dependencies [91facae]
|
|
4159
|
+
- Updated dependencies [b38014e]
|
|
4160
|
+
- Updated dependencies [474797d]
|
|
4161
|
+
- Updated dependencies [704e695]
|
|
4162
|
+
- Updated dependencies [a407bd6]
|
|
4163
|
+
- Updated dependencies [317dbce]
|
|
4164
|
+
- Updated dependencies [309728c]
|
|
4165
|
+
- Updated dependencies [c03d03b]
|
|
4166
|
+
- Updated dependencies [aa08d7e]
|
|
4167
|
+
- Updated dependencies [3a43a15]
|
|
4168
|
+
- Updated dependencies [868e825]
|
|
4169
|
+
- Updated dependencies [f76f436]
|
|
4170
|
+
- Updated dependencies [ce45a03]
|
|
4171
|
+
- Updated dependencies [421544b]
|
|
4172
|
+
- Updated dependencies [fb01022]
|
|
4173
|
+
- Updated dependencies [e9d9212]
|
|
4174
|
+
- Updated dependencies [ecfb693]
|
|
4175
|
+
- Updated dependencies [639ca9d]
|
|
4176
|
+
- Updated dependencies [2152962]
|
|
4177
|
+
- Updated dependencies [abc1b18]
|
|
4178
|
+
- Updated dependencies [81a51db]
|
|
4179
|
+
- Updated dependencies [67749c7]
|
|
4180
|
+
- Updated dependencies [507b61b]
|
|
4181
|
+
- Updated dependencies [512c84b]
|
|
4182
|
+
- Updated dependencies [c300267]
|
|
4183
|
+
- Updated dependencies [fb3a101]
|
|
4184
|
+
- Updated dependencies [d4733f2]
|
|
4185
|
+
- Updated dependencies [7c9145f]
|
|
4186
|
+
- Updated dependencies [1570eac]
|
|
4187
|
+
- Updated dependencies [f391ede]
|
|
4188
|
+
- Updated dependencies [f5cfbbd]
|
|
4189
|
+
- Updated dependencies [f5cfbbd]
|
|
4190
|
+
- Updated dependencies [8b532cb]
|
|
4191
|
+
- Updated dependencies [64c3cdd]
|
|
4192
|
+
- Updated dependencies [4d65991]
|
|
4193
|
+
- Updated dependencies [c42554e]
|
|
4194
|
+
- Updated dependencies [3e71b26]
|
|
4195
|
+
- Updated dependencies [b89583b]
|
|
4196
|
+
- Updated dependencies [70c4523]
|
|
4197
|
+
- Updated dependencies [555b4ec]
|
|
4198
|
+
- Updated dependencies [64f1cf1]
|
|
4199
|
+
- Updated dependencies [da5e4f6]
|
|
4200
|
+
- Updated dependencies [1ccfc23]
|
|
4201
|
+
- Updated dependencies [542718f]
|
|
4202
|
+
- Updated dependencies [7f27bc5]
|
|
4203
|
+
- Updated dependencies [0a174f3]
|
|
4204
|
+
- Updated dependencies [676f677]
|
|
4205
|
+
- Updated dependencies [f95b140]
|
|
4206
|
+
- Updated dependencies [541ce4e]
|
|
4207
|
+
- Updated dependencies [6479086]
|
|
4208
|
+
- Updated dependencies [d79f525]
|
|
4209
|
+
- Updated dependencies [d1865d2]
|
|
4210
|
+
- Updated dependencies [88a629d]
|
|
4211
|
+
- Updated dependencies [cdd2542]
|
|
4212
|
+
- Updated dependencies [ab77513]
|
|
4213
|
+
- Updated dependencies [55ba3ff]
|
|
4214
|
+
- Updated dependencies [3ecc369]
|
|
4215
|
+
- Updated dependencies [f1190b0]
|
|
4216
|
+
- Updated dependencies [561abef]
|
|
4217
|
+
- Updated dependencies [ef52001]
|
|
4218
|
+
- Updated dependencies [6a4680b]
|
|
4219
|
+
- Updated dependencies [c3a4273]
|
|
4220
|
+
- Updated dependencies [abf710d]
|
|
4221
|
+
- Updated dependencies [093af32]
|
|
4222
|
+
- Updated dependencies [1bd1be7]
|
|
4223
|
+
- Updated dependencies [d234fa9]
|
|
4224
|
+
- Updated dependencies [adf5812]
|
|
4225
|
+
- Updated dependencies [0eaed83]
|
|
4226
|
+
- Updated dependencies [e36acd4]
|
|
4227
|
+
- Updated dependencies [5058336]
|
|
4228
|
+
- Updated dependencies [2f6b2bf]
|
|
4229
|
+
- Updated dependencies [2028b31]
|
|
4230
|
+
- Updated dependencies [63601ab]
|
|
4231
|
+
- Updated dependencies [c372b29]
|
|
4232
|
+
- Updated dependencies [152f0a7]
|
|
4233
|
+
- Updated dependencies [8693b85]
|
|
4234
|
+
- Updated dependencies [e82dad1]
|
|
4235
|
+
- Updated dependencies [58b7b3d]
|
|
4236
|
+
- Updated dependencies [84defab]
|
|
4237
|
+
- Updated dependencies [681d3f1]
|
|
4238
|
+
- Updated dependencies [969d4f2]
|
|
4239
|
+
- Updated dependencies [f3bc481]
|
|
4240
|
+
- Updated dependencies [b79aac2]
|
|
4241
|
+
- Updated dependencies [93fc0e7]
|
|
4242
|
+
- Updated dependencies [a4b723f]
|
|
4243
|
+
- Updated dependencies [2b10ca0]
|
|
4244
|
+
- Updated dependencies [7db4a81]
|
|
4245
|
+
- Updated dependencies [19a0b0e]
|
|
4246
|
+
- Updated dependencies [526fc11]
|
|
4247
|
+
- Updated dependencies [f8e3e9a]
|
|
4248
|
+
- Updated dependencies [b9d47ec]
|
|
4249
|
+
- Updated dependencies [3b6bc69]
|
|
4250
|
+
- Updated dependencies [6214db6]
|
|
4251
|
+
- Updated dependencies [6732df4]
|
|
4252
|
+
- Updated dependencies [fe9e0d0]
|
|
4253
|
+
- Updated dependencies [63fb72c]
|
|
4254
|
+
- Updated dependencies [804831c]
|
|
4255
|
+
- Updated dependencies [279e48e]
|
|
4256
|
+
- Updated dependencies [8700d6d]
|
|
4257
|
+
- Updated dependencies [8db2a0f]
|
|
4258
|
+
- Updated dependencies [689953a]
|
|
4259
|
+
- Updated dependencies [30443fb]
|
|
4260
|
+
- Updated dependencies [8d3dbb2]
|
|
4261
|
+
- Updated dependencies [efc1c9c]
|
|
4262
|
+
- Updated dependencies [da45e6b]
|
|
4263
|
+
- Updated dependencies [7533465]
|
|
4264
|
+
- Updated dependencies [835f0f3]
|
|
4265
|
+
- Updated dependencies [6d5db7b]
|
|
4266
|
+
- Updated dependencies [a9d97be]
|
|
4267
|
+
- Updated dependencies [ed35b44]
|
|
4268
|
+
- Updated dependencies [9ba7e9c]
|
|
4269
|
+
- Updated dependencies [729e851]
|
|
4270
|
+
- Updated dependencies [96919a4]
|
|
4271
|
+
- Updated dependencies [345e24a]
|
|
4272
|
+
- Updated dependencies [20b507a]
|
|
4273
|
+
- Updated dependencies [2e471dc]
|
|
4274
|
+
- Updated dependencies [6748587]
|
|
4275
|
+
- Updated dependencies [be50942]
|
|
4276
|
+
- Updated dependencies [775e079]
|
|
4277
|
+
- Updated dependencies [7e8b3c0]
|
|
4278
|
+
- Updated dependencies [53374dc]
|
|
4279
|
+
- Updated dependencies [f6fb83f]
|
|
4280
|
+
- Updated dependencies [2049b03]
|
|
4281
|
+
- Updated dependencies [2bf34f7]
|
|
4282
|
+
- Updated dependencies [15b33ae]
|
|
4283
|
+
- Updated dependencies [7cbc724]
|
|
4284
|
+
- Updated dependencies [4a94c38]
|
|
4285
|
+
- Updated dependencies [7098eed]
|
|
4286
|
+
- Updated dependencies [3df7c5c]
|
|
4287
|
+
- Updated dependencies [fb91ac9]
|
|
4288
|
+
- Updated dependencies [fb91ac9]
|
|
4289
|
+
- Updated dependencies [641fb55]
|
|
4290
|
+
- Updated dependencies [8524372]
|
|
4291
|
+
- Updated dependencies [7cbefa5]
|
|
4292
|
+
- Updated dependencies [72d6587]
|
|
4293
|
+
- Updated dependencies [a272a4f]
|
|
4294
|
+
- Updated dependencies [55f39ee]
|
|
4295
|
+
- Updated dependencies [0ce32d5]
|
|
4296
|
+
- Updated dependencies [0970a0e]
|
|
4297
|
+
- Updated dependencies [e427e9c]
|
|
4298
|
+
- Updated dependencies [bbc9dc3]
|
|
4299
|
+
- Updated dependencies [02f1813]
|
|
4300
|
+
- Updated dependencies [1ef89c0]
|
|
4301
|
+
- Updated dependencies [ba0b61a]
|
|
4302
|
+
- Updated dependencies [ba0b61a]
|
|
4303
|
+
- Updated dependencies [ac716ff]
|
|
4304
|
+
- Updated dependencies [e05553c]
|
|
4305
|
+
- Updated dependencies [f0f3cd5]
|
|
4306
|
+
- Updated dependencies [ab856ed]
|
|
4307
|
+
- Updated dependencies [f0f2046]
|
|
4308
|
+
- Updated dependencies [20f3e65]
|
|
4309
|
+
- Updated dependencies [bbba098]
|
|
4310
|
+
- Updated dependencies [87af769]
|
|
4311
|
+
- Updated dependencies [3be720e]
|
|
4312
|
+
- Updated dependencies [43c0d17]
|
|
4313
|
+
- Updated dependencies [c3df43a]
|
|
4314
|
+
- Updated dependencies [d16d0e9]
|
|
4315
|
+
- Updated dependencies [bbe57fd]
|
|
4316
|
+
- Updated dependencies [272a530]
|
|
4317
|
+
- Updated dependencies [1779e8d]
|
|
4318
|
+
- Updated dependencies [9aa2a57]
|
|
4319
|
+
- Updated dependencies [f7fcc2c]
|
|
4320
|
+
- Updated dependencies [f0f4d6c]
|
|
4321
|
+
- Updated dependencies [4128188]
|
|
4322
|
+
- Updated dependencies [b253c4e]
|
|
4323
|
+
- Updated dependencies [78a9c67]
|
|
4324
|
+
- Updated dependencies [2e97c8c]
|
|
4325
|
+
- Updated dependencies [4a7ef0d]
|
|
4326
|
+
- Updated dependencies [4598f6d]
|
|
4327
|
+
- Updated dependencies [dea17b4]
|
|
4328
|
+
- Updated dependencies [89bb77a]
|
|
4329
|
+
- Updated dependencies [06611e4]
|
|
4330
|
+
- Updated dependencies [44152c4]
|
|
4331
|
+
- Updated dependencies [3939545]
|
|
4332
|
+
- Updated dependencies [66abbde]
|
|
4333
|
+
- Updated dependencies [dc3893d]
|
|
4334
|
+
- Updated dependencies [1bbaa16]
|
|
4335
|
+
- Updated dependencies [6ee259a]
|
|
4336
|
+
- Updated dependencies [7649f43]
|
|
4337
|
+
- Updated dependencies [e708426]
|
|
4338
|
+
- Updated dependencies [3b6d53b]
|
|
4339
|
+
- Updated dependencies [e22fa12]
|
|
4340
|
+
- Updated dependencies [1560d46]
|
|
4341
|
+
- Updated dependencies [276d174]
|
|
4342
|
+
- Updated dependencies [2982ed9]
|
|
4343
|
+
- Updated dependencies [741864f]
|
|
4344
|
+
- Updated dependencies [a8198de]
|
|
4345
|
+
- Updated dependencies [0c789a4]
|
|
4346
|
+
- Updated dependencies [05a49f2]
|
|
4347
|
+
- Updated dependencies [b234a84]
|
|
4348
|
+
- Updated dependencies [a78cd37]
|
|
4349
|
+
- Updated dependencies [5ea623e]
|
|
4350
|
+
- Updated dependencies [4d7d322]
|
|
4351
|
+
- Updated dependencies [5eabe86]
|
|
4352
|
+
- Updated dependencies [ca5d671]
|
|
4353
|
+
- Updated dependencies [32bf2d6]
|
|
4354
|
+
- Updated dependencies [ff0c384]
|
|
4355
|
+
- Updated dependencies [af4fb29]
|
|
4356
|
+
- Updated dependencies [c698a81]
|
|
4357
|
+
- Updated dependencies [ff5ef1c]
|
|
4358
|
+
- Updated dependencies [befd40c]
|
|
4359
|
+
- Updated dependencies [9a97800]
|
|
4360
|
+
- Updated dependencies [6bca0e4]
|
|
4361
|
+
- Updated dependencies [81c0bc4]
|
|
4362
|
+
- Updated dependencies [3c76801]
|
|
4363
|
+
- Updated dependencies [60500cb]
|
|
4364
|
+
- Updated dependencies [2fcefb9]
|
|
4365
|
+
- Updated dependencies [77f846a]
|
|
4366
|
+
- Updated dependencies [bc5870c]
|
|
4367
|
+
- Updated dependencies [b55a346]
|
|
4368
|
+
- Updated dependencies [065bba7]
|
|
4369
|
+
- Updated dependencies [f760064]
|
|
4370
|
+
- Updated dependencies [dd19463]
|
|
4371
|
+
- Updated dependencies [6791717]
|
|
4372
|
+
- Updated dependencies [8ea3bee]
|
|
4373
|
+
- Updated dependencies [100547e]
|
|
4374
|
+
- Updated dependencies [3a58149]
|
|
4375
|
+
- Updated dependencies [6d1c155]
|
|
4376
|
+
- Updated dependencies [d7573b3]
|
|
4377
|
+
- Updated dependencies [bf3edfe]
|
|
4378
|
+
- Updated dependencies [2c8474c]
|
|
4379
|
+
- Updated dependencies [6ce89da]
|
|
4380
|
+
- Updated dependencies [0e05aac]
|
|
4381
|
+
- Updated dependencies [ae61ad4]
|
|
4382
|
+
- Updated dependencies [5aed9e4]
|
|
4383
|
+
- Updated dependencies [83c77dc]
|
|
4384
|
+
- Updated dependencies [3c9fca3]
|
|
4385
|
+
- Updated dependencies [18a8e7d]
|
|
4386
|
+
- Updated dependencies [e7957ab]
|
|
4387
|
+
- Updated dependencies [f7e34ca]
|
|
4388
|
+
- Updated dependencies [e719ebd]
|
|
4389
|
+
- Updated dependencies [516583b]
|
|
4390
|
+
- Updated dependencies [f9e4f91]
|
|
4391
|
+
- Updated dependencies [6ef48b1]
|
|
4392
|
+
- Updated dependencies [58be55e]
|
|
4393
|
+
- Updated dependencies [fa429cf]
|
|
4394
|
+
- Updated dependencies [ed8df3e]
|
|
4395
|
+
- Updated dependencies [fe76ece]
|
|
4396
|
+
- Updated dependencies [8b446f5]
|
|
4397
|
+
- Updated dependencies [8e74b27]
|
|
4398
|
+
- Updated dependencies [7102b20]
|
|
4399
|
+
- Updated dependencies [8ebd57f]
|
|
4400
|
+
- Updated dependencies [968dc1e]
|
|
4401
|
+
- Updated dependencies [9a1fb41]
|
|
4402
|
+
- Updated dependencies [617707a]
|
|
4403
|
+
- Updated dependencies [c40f3b8]
|
|
4404
|
+
- Updated dependencies [58770f3]
|
|
4405
|
+
- Updated dependencies [aefe428]
|
|
4406
|
+
- Updated dependencies [485f096]
|
|
4407
|
+
- Updated dependencies [7357447]
|
|
4408
|
+
- Updated dependencies [199d31b]
|
|
4409
|
+
- Updated dependencies [b655a9d]
|
|
4410
|
+
- Updated dependencies [a865c73]
|
|
4411
|
+
- Updated dependencies [3e01cb5]
|
|
4412
|
+
- Updated dependencies [7138bc1]
|
|
4413
|
+
- Updated dependencies [cef27e2]
|
|
4414
|
+
- Updated dependencies [4e8622b]
|
|
4415
|
+
- Updated dependencies [dffd752]
|
|
4416
|
+
- Updated dependencies [06973aa]
|
|
4417
|
+
- Updated dependencies [50798f3]
|
|
4418
|
+
- Updated dependencies [6a576c9]
|
|
4419
|
+
- Updated dependencies [105f3c5]
|
|
4420
|
+
- Updated dependencies [3ccd9e8]
|
|
4421
|
+
- Updated dependencies [689b979]
|
|
4422
|
+
- Updated dependencies [c70f865]
|
|
4423
|
+
- Updated dependencies [e546222]
|
|
4424
|
+
- Updated dependencies [fd13f52]
|
|
4425
|
+
- Updated dependencies [d7bd274]
|
|
4426
|
+
- Updated dependencies [98c3a74]
|
|
4427
|
+
- Updated dependencies [fffa30d]
|
|
4428
|
+
- Updated dependencies [e4e9557]
|
|
4429
|
+
- Updated dependencies [7a28e1e]
|
|
4430
|
+
- Updated dependencies [ebce5a3]
|
|
4431
|
+
- Updated dependencies [fb336df]
|
|
4432
|
+
- Updated dependencies [4dc80d0]
|
|
4433
|
+
- Updated dependencies [9d9040d]
|
|
4434
|
+
- Updated dependencies [20e317c]
|
|
4435
|
+
- Updated dependencies [0fce2ef]
|
|
4436
|
+
- Updated dependencies [42df928]
|
|
4437
|
+
- Updated dependencies [0e2ddd4]
|
|
4438
|
+
- Updated dependencies [b7479ab]
|
|
4439
|
+
- Updated dependencies [9850c6e]
|
|
4440
|
+
- Updated dependencies [de570cc]
|
|
4441
|
+
- Updated dependencies [b2ea297]
|
|
4442
|
+
- Updated dependencies [5b5a5c3]
|
|
4443
|
+
- Updated dependencies [14582b8]
|
|
4444
|
+
- Updated dependencies [51e144e]
|
|
4445
|
+
- Updated dependencies [19cbf10]
|
|
4446
|
+
- Updated dependencies [b6e83be]
|
|
4447
|
+
- Updated dependencies [ab92940]
|
|
4448
|
+
- Updated dependencies [a691c0b]
|
|
4449
|
+
- Updated dependencies [0b1326d]
|
|
4450
|
+
- Updated dependencies [1e66879]
|
|
4451
|
+
- Updated dependencies [c5200f0]
|
|
4452
|
+
- Updated dependencies [af3861f]
|
|
4453
|
+
- Updated dependencies [2609812]
|
|
4454
|
+
- Updated dependencies [515f171]
|
|
4455
|
+
- Updated dependencies [1f4e029]
|
|
4456
|
+
- Updated dependencies [4f14ad7]
|
|
4457
|
+
- Updated dependencies [258d264]
|
|
4458
|
+
- Updated dependencies [cac64b3]
|
|
4459
|
+
- Updated dependencies [4bb940b]
|
|
4460
|
+
- Updated dependencies [8033ad1]
|
|
4461
|
+
- Updated dependencies [fa140b8]
|
|
4462
|
+
- Updated dependencies [71cba28]
|
|
4463
|
+
- Updated dependencies [190fbd0]
|
|
4464
|
+
- Updated dependencies [c00bf28]
|
|
4465
|
+
- Updated dependencies [93127bd]
|
|
4466
|
+
- Updated dependencies [f2158ec]
|
|
4467
|
+
- Updated dependencies [759606e]
|
|
4468
|
+
- Updated dependencies [fd8dace]
|
|
4469
|
+
- Updated dependencies [72ffc34]
|
|
4470
|
+
- Updated dependencies [a51fa0c]
|
|
4471
|
+
- Updated dependencies [51f3d8d]
|
|
4472
|
+
- Updated dependencies [bf28341]
|
|
4473
|
+
- Updated dependencies [78cbdb5]
|
|
4474
|
+
- Updated dependencies [b7543a9]
|
|
4475
|
+
- Updated dependencies [6c6cee7]
|
|
4476
|
+
- Updated dependencies [42887e0]
|
|
4477
|
+
- Updated dependencies [f1690d4]
|
|
4478
|
+
- Updated dependencies [83fe6e7]
|
|
4479
|
+
- Updated dependencies [d1ab06f]
|
|
4480
|
+
- Updated dependencies [38a9568]
|
|
4481
|
+
- Updated dependencies [f90b8fb]
|
|
4482
|
+
- Updated dependencies [91783c4]
|
|
4483
|
+
- Updated dependencies [982885d]
|
|
4484
|
+
- Updated dependencies [dba7d84]
|
|
4485
|
+
- Updated dependencies [ca39427]
|
|
4486
|
+
- Updated dependencies [bd09957]
|
|
4487
|
+
- Updated dependencies [5a07e67]
|
|
4488
|
+
- Updated dependencies [2d36552]
|
|
4489
|
+
- Updated dependencies [45d8288]
|
|
4490
|
+
- Updated dependencies [b2437a7]
|
|
4491
|
+
- Updated dependencies [f157423]
|
|
4492
|
+
- Updated dependencies [7a90afd]
|
|
4493
|
+
- Updated dependencies [eddc1dd]
|
|
4494
|
+
- Updated dependencies [490f482]
|
|
4495
|
+
- Updated dependencies [27308c5]
|
|
4496
|
+
- Updated dependencies [8689166]
|
|
4497
|
+
- Updated dependencies [c9327c9]
|
|
4498
|
+
- Updated dependencies [920165d]
|
|
4499
|
+
- Updated dependencies [9101be5]
|
|
4500
|
+
- Updated dependencies [f53a8d0]
|
|
4501
|
+
- Updated dependencies [30266cf]
|
|
4502
|
+
- Updated dependencies [968dc1e]
|
|
4503
|
+
- Updated dependencies [57f9b07]
|
|
4504
|
+
- Updated dependencies [3c73d99]
|
|
4505
|
+
- Updated dependencies [d91aed9]
|
|
4506
|
+
- Updated dependencies [ed71d9e]
|
|
4507
|
+
- Updated dependencies [7776fc2]
|
|
4508
|
+
- Updated dependencies [e76634c]
|
|
4509
|
+
- Updated dependencies [c86185e]
|
|
4510
|
+
- Updated dependencies [fb96ecb]
|
|
4511
|
+
- Updated dependencies [1170ed1]
|
|
4512
|
+
- Updated dependencies [92814db]
|
|
4513
|
+
- Updated dependencies [4d73b07]
|
|
4514
|
+
- @object-ui/react@17.7.0
|
|
4515
|
+
- @object-ui/core@17.7.0
|
|
4516
|
+
- @object-ui/types@17.7.0
|
|
4517
|
+
- @object-ui/i18n@17.7.0
|
|
4518
|
+
- @object-ui/components@17.7.0
|
|
4519
|
+
- @object-ui/fields@17.7.0
|
|
4520
|
+
- @object-ui/permissions@17.7.0
|
|
4521
|
+
|
|
3
4522
|
## 17.6.0
|
|
4
4523
|
|
|
5
4524
|
### Minor Changes
|