@object-ui/plugin-charts 17.3.0 → 17.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,645 @@
1
1
  # @object-ui/plugin-charts
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9ce096f: Give a chart bucket an identity distinct from its display label
8
+
9
+ objectui#4508. `buildChartSeries` used the bucket's DISPLAY string as the
10
+ bucket's own key, so two pairs of genuinely different groups were conflated —
11
+ and the segment click that drills a bar back to its records inherited both
12
+ conflations. The maintainer ruling (2026-08-14) approved the sentinel-identity
13
+ direction, aligning the chart branch with the distinct-bucket-id form the pivot
14
+ TABLE (`buildPivot`) already uses over the same dataset rows.
15
+
16
+ Two collisions, one cause:
17
+
18
+ - **A null group and an empty-string group drew ONE bar.** The pivot branch
19
+ keyed buckets by `String(xRaw ?? '')`, which spells `null` and `''`
20
+ identically. The bar took its label from whichever row created the bucket, and
21
+ the other group's segment then resolved to no row at all — a visible bar whose
22
+ click did nothing.
23
+ - **A record whose stored value spells the bucket label stole the null bucket's
24
+ drill.** A row storing the literal text `(None)` (or any localized
25
+ `chart.nullCategory` — `(未指定)` and the other nine packs) kept its own
26
+ bucket, so two bars carried the same axis text and BOTH resolved to the first.
27
+ That one is a wrong drill, not a dead one: clicking the null bucket's bar
28
+ opened the drawer on another group's records.
29
+
30
+ What changed:
31
+
32
+ - **`chartBucketId`** (`@object-ui/core`) is the bucket identity — the SAME
33
+ encoder `buildPivot` keys its buckets with (`pivotBucketId` over
34
+ `pivotDimensionValue`), so the two surfaces stop answering one question two
35
+ ways. The pivot branch now buckets by it, which is what makes null and `''`
36
+ two groups again.
37
+ - **`CHART_BUCKET_ID_KEY`** carries that identity on an emitted row, written
38
+ exactly where two DISTINCT buckets paint the same axis text — the complete set
39
+ of cases where the display string cannot name what was clicked. An ordinary
40
+ chart's rows are returned untouched (by identity), so no renderer-internal key
41
+ reaches an authoring surface.
42
+ - **`findChartSeriesRow`** takes that identity back as `options.bucketId` and
43
+ treats it as authoritative. The renderers forward it: the drill event gains
44
+ `categoryId` (`ChartSegmentClickEvent`, now declared once in
45
+ `@object-ui/core` instead of inline in three packages), `AdvancedChartImpl`
46
+ reads it off the clicked row on the cartesian, pie and funnel paths, and
47
+ `DatasetWidget.handleChartDrill` hands it to the lookup.
48
+
49
+ Behaviour change worth noting: an empty-string category no longer resolves to a
50
+ null-valued row. That tolerance was justified as the drill layer's own spelling
51
+ of "no group value", but no producer of this lookup's `category` writes it,
52
+ while `''` IS the axis text a genuine empty-string group paints — so the
53
+ tolerance was giving that group's bar a different group's records. A host that
54
+ forwards no `categoryId` keeps its existing drill unchanged.
55
+
56
+ ### Patch Changes
57
+
58
+ - e132433: A grouped chart whose SECOND dimension was never projected says so instead of
59
+ drawing an empty frame.
60
+
61
+ "Cannot know refuses loudly" was answered on the first dimension only.
62
+ `AdvancedChartImpl`'s `hasNoCategoryKey` (framework#4033) names an unprojected
63
+ x-axis dimension rather than drawing a bare axis; the series axis had no
64
+ counterpart, so a pivot whose second dimension was absent from the result rows
65
+ produced `series: []` and rendered axes, grid, tooltip and legend around zero
66
+ marks — indistinguishable, to the author, from "no data matched".
67
+
68
+ Such a chart now renders the same explanatory placeholder
69
+ (`data-chart-error="no-plottable-series"`) and logs the same diagnostic pair the
70
+ category-axis guard logs: the axis it did plot, and the keys its rows actually
71
+ carry.
72
+
73
+ The three-way distinction is unchanged and pinned: null / empty-string group
74
+ values still DRAW (they are real groups with real buckets), a partially
75
+ projected group key still draws what projects — mirroring the category axis,
76
+ which refuses only when NOT ONE row carries the key — and an ordinary pivot
77
+ renders unchanged. The refusal is limited to the families whose marks come from
78
+ `series` and nothing else (bar, horizontal-bar, line, area, combo); pie, donut,
79
+ funnel, radar and scatter draw from a `value` column with no series declared, so
80
+ they are untouched. A caller that computed no series binding at all
81
+ (`series === undefined`) is also untouched.
82
+ - f95434b: Combo charts drill from their marks
83
+
84
+ objectui#4692, ruled Option B. `AdvancedChartImpl` built `cartesianClickProps` once and
85
+ applied it to exactly one element — the final cartesian `ChartComponent`. The `combo`
86
+ branch returns earlier, from its own `ComposedChart`, which was rendered with `data` and
87
+ no click props at all, so a combo chart fired `onChartClick` never: not on a mark, not on
88
+ the axis. Its marks are the same `Bar` / `Line` / `Area` components the drillable branch
89
+ renders.
90
+
91
+ The trap that made this worth fixing rather than documenting is that the family is
92
+ **derived**, not only authored: `effectiveChartFamily` resolves a chart to `combo`
93
+ whenever its series declare different families (objectui#2945), so adding `type: 'line'`
94
+ to one series of a drillable bar chart silently turned that chart's drill-through off —
95
+ nothing in the authored spec said drill had been touched, and nothing errored.
96
+
97
+ A combo's `Bar` / `Line` / `Area` marks now emit `{ category, categoryId, series, value }`
98
+ with the same semantics the plain cartesian branch gives, reusing the item-level
99
+ series-identity machinery from objectui#4672 / objectui#4682: the mark handler records the
100
+ series it was rendered with, the chart-level handler composes the one event, so a gesture
101
+ still produces exactly one `onChartClick`. Retyping one series now changes that series'
102
+ mark and nothing else.
103
+
104
+ **Only the marks drill.** A click on a combo's plot surface or axis stays silent, where
105
+ the plain cartesian branch falls back to its axis-level answer. A combo plots several
106
+ measures on one plot, so a surface click there has no single series to report and the
107
+ fallback would have to invent one — the same reasoning objectui#4672's ruling gave the
108
+ pivoted case. Combo also carries no chart-wide pointer cursor for that reason; the
109
+ affordance sits on the marks that answer.
110
+
111
+ Radar is now the one cartesian-adjacent family with no click wiring. The `onChartClick`
112
+ doc comment, corrected in objectui#4705 to say combo was a no-op, states the new rule and
113
+ its one deliberate exception.
114
+ - e05db88: A clicked cartesian mark names its own series, and the drill title reads its label
115
+
116
+ objectui#4672, objectui#4682.
117
+
118
+ **The dead pivoted drill.** objectui#4680 fixed what a cartesian click could
119
+ read out of recharts 3's `MouseHandlerDataParam`, and measured the wall it could
120
+ not get past: a chart-level click is an AXIS interaction, and recharts
121
+ dispatches those with `activeDataKey` hard-coded `undefined`, because the shared
122
+ cursor spans every series at that tick. A pivoted dataset chart — 2 dimensions,
123
+ 1 measure, the shape ADR-0021 introduced — needs the series to resolve its drill
124
+ row, so every segment of every such dashboard chart stayed a dead click. The
125
+ series was left unresolved rather than guessed, and the card carried the rest.
126
+
127
+ The answer is the mark itself. This renderer draws the `Bar` / `Line` / `Area`,
128
+ so an item-level `onClick` closes over the very `dataKey` it was rendered with —
129
+ the series is statically known, not inferred from tooltip state.
130
+
131
+ Both handlers fire for one gesture (measured: item first, chart second, sharing
132
+ one `nativeEvent` object), so the item handler does not emit. It RECORDS its
133
+ series, stamped with that gesture, and the chart-level handler composes the one
134
+ event. That is the double-fire answer and the additive property together:
135
+
136
+ - **one click, one drill event**, because there is one emit site — not a second
137
+ event suppressed after the fact;
138
+ - **a click that lands on no mark is untouched**: it records nothing and falls
139
+ through to the objectui#4680 axis answer exactly as shipped — category, bucket
140
+ identity, and the series only where one series is plotted. Empty plot area
141
+ stays category-only, and "drill the whole category" was rejected as a
142
+ different product question. Nothing that resolved before stops resolving; a
143
+ line's `dot={false}` stroke simply GAINS the exact series where it is hit;
144
+ - pairing on the shared DOM event rather than on a flag means a record left by
145
+ one gesture can never be adopted by a later click.
146
+
147
+ The clicked key is forwarded exactly as rendered, `''` included: the
148
+ empty-string second-dimension group draws its own bar since objectui#4673, and
149
+ `''` is falsy, so a truthiness test on the way out would send no series at all
150
+ and leave that bar's drill standing on the reader's coercion instead of on what
151
+ was clicked.
152
+
153
+ **The opaque drill title.** `ChartSegmentClickEvent` gains `seriesLabel`, and
154
+ `DatasetWidget`'s drill drawer titles itself from `seriesLabel ?? series`.
155
+ `ev.series` stays the LOOKUP key — `findChartSeriesRow` resolves it through the
156
+ same assignment `buildChartSeries` made — and only the title reads the label.
157
+
158
+ The two strings are equal for every ordinary group, which is why reading the key
159
+ as a title went unnoticed. They part company when a group's label cannot name
160
+ it: the null bucket beside a record whose stored value literally spells
161
+ `(None)`, which is objectui#4508's collision on the series axis, reachable since
162
+ objectui#4673. Both groups then key by `chartBucketId`, and the drawer opened on
163
+ the right records under the title `Backlog / [null]`. An internal id where a
164
+ label belongs reads as broken DATA rather than as a broken title.
165
+
166
+ Neither string can do the other's job, which is why this is a second field
167
+ rather than a change to the first: the label is not resolvable (it is exactly
168
+ what the colliding groups share) and the key is not showable. `seriesLabel` is
169
+ optional and absent wherever a renderer resolved no label, so every other
170
+ chart's title is byte-identical.
171
+ - d298be8: Cartesian chart clicks report the clicked series and value again
172
+
173
+ objectui#4672. `AdvancedChartImpl`'s chart-level click handler built its drill
174
+ event from `payload.activePayload[0]` — a **recharts 2** field. This package is
175
+ on recharts 3, which hands a chart-level `onClick` a `MouseHandlerDataParam`:
176
+ `{ activeCoordinate, activeDataKey, activeIndex, activeLabel, activeTooltipIndex,
177
+ isTooltipActive }`, and nothing else. `activePayload` appears nowhere in the
178
+ shipped library, so the read was `undefined` on **every** cartesian click and
179
+ every bar / line / area drill event carried `series: undefined, value: undefined`.
180
+ Nothing went red: the payload is typed `any` at the call site, and every existing
181
+ drill test either calls the pure lookup directly or stubs the chart.
182
+
183
+ The handler now works from the payload recharts 3 actually sends:
184
+
185
+ - **The value** is read off the clicked row — `data[activeTooltipIndex]`, the
186
+ array this component was given — for the resolved measure, the same way the
187
+ bucket identity has been read since objectui#4508.
188
+ - **The series** comes from `activeDataKey` when the payload carries one, and
189
+ otherwise from the chart's own series list when it plots exactly one series,
190
+ where the clicked column can belong to nothing else.
191
+ - **A click with no active tick** (the plot margins, an axis label) resolves to
192
+ no row instead of to bucket zero. recharts reports a **null** index there, not
193
+ an absent one, and `Number(null)` is `0` — so such a click used to drill the
194
+ first bucket's records. That is a wrong drill, not a dead one.
195
+
196
+ A drill on a single-measure chart therefore names its measure and carries its
197
+ value again — `resolveDrillTitle` composes the drawer title from them, and an
198
+ authored drill filter can reference `${event.value}`.
199
+
200
+ **Still open, deliberately:** a chart plotting several series under the default
201
+ SHARED cursor. Measured against recharts 3.10.1, an axis interaction is
202
+ dispatched with `activeDataKey` hard-coded `undefined` (bar, line and area
203
+ alike, on the mark and on empty plot area), so the payload names no series at
204
+ all — and a pivoted dataset chart's drill lookup requires one. The series is
205
+ left unresolved rather than guessed: naming a series the user did not click
206
+ drills to another group's records, which is worse than the dead click. Resolving
207
+ it needs the clicked mark rather than this payload; objectui#4672 carries that
208
+ half.
209
+ - Updated dependencies [88085e3]
210
+ - Updated dependencies [69251bf]
211
+ - Updated dependencies [57e668f]
212
+ - Updated dependencies [516663d]
213
+ - Updated dependencies [41ac1b7]
214
+ - Updated dependencies [1eaf0a1]
215
+ - Updated dependencies [460c4d0]
216
+ - Updated dependencies [0ae27f7]
217
+ - Updated dependencies [2533ec5]
218
+ - Updated dependencies [78c0f9a]
219
+ - Updated dependencies [bbe8b86]
220
+ - Updated dependencies [8477be5]
221
+ - Updated dependencies [279fb13]
222
+ - Updated dependencies [2e82ab2]
223
+ - Updated dependencies [ad07b65]
224
+ - Updated dependencies [41f498b]
225
+ - Updated dependencies [ef0d150]
226
+ - Updated dependencies [f34226e]
227
+ - Updated dependencies [564b605]
228
+ - Updated dependencies [e1d4251]
229
+ - Updated dependencies [40d3a33]
230
+ - Updated dependencies [8b9dc62]
231
+ - Updated dependencies [1184192]
232
+ - Updated dependencies [a2a9747]
233
+ - Updated dependencies [a1609a6]
234
+ - Updated dependencies [53f23bc]
235
+ - Updated dependencies [c4533dc]
236
+ - Updated dependencies [be60815]
237
+ - Updated dependencies [37f6844]
238
+ - Updated dependencies [93de4f6]
239
+ - Updated dependencies [2b50261]
240
+ - Updated dependencies [384f30d]
241
+ - Updated dependencies [ac600e5]
242
+ - Updated dependencies [97fba31]
243
+ - Updated dependencies [232f61a]
244
+ - Updated dependencies [d374caf]
245
+ - Updated dependencies [5673576]
246
+ - Updated dependencies [c1ef923]
247
+ - Updated dependencies [911ceaa]
248
+ - Updated dependencies [98eab36]
249
+ - Updated dependencies [af5e292]
250
+ - Updated dependencies [3fbbea1]
251
+ - Updated dependencies [7f96b10]
252
+ - Updated dependencies [167ec42]
253
+ - Updated dependencies [616a2a5]
254
+ - Updated dependencies [0046d8f]
255
+ - Updated dependencies [f1d4748]
256
+ - Updated dependencies [bea374e]
257
+ - Updated dependencies [b1119ec]
258
+ - Updated dependencies [9f23d2b]
259
+ - Updated dependencies [578e025]
260
+ - Updated dependencies [af025ee]
261
+ - Updated dependencies [d109a4d]
262
+ - Updated dependencies [598c89a]
263
+ - Updated dependencies [4a0bd17]
264
+ - Updated dependencies [b8b9af4]
265
+ - Updated dependencies [31676be]
266
+ - Updated dependencies [8c0d52e]
267
+ - Updated dependencies [aff10e2]
268
+ - Updated dependencies [70a774b]
269
+ - Updated dependencies [9ce096f]
270
+ - Updated dependencies [e05db88]
271
+ - Updated dependencies [7458a41]
272
+ - Updated dependencies [ad13d63]
273
+ - Updated dependencies [5ffcc14]
274
+ - Updated dependencies [d971e51]
275
+ - Updated dependencies [97abb24]
276
+ - Updated dependencies [deb157a]
277
+ - Updated dependencies [9c60144]
278
+ - Updated dependencies [d2ce342]
279
+ - Updated dependencies [9695da7]
280
+ - Updated dependencies [75444e3]
281
+ - Updated dependencies [58b8346]
282
+ - Updated dependencies [2d0bd16]
283
+ - Updated dependencies [a9e17b4]
284
+ - Updated dependencies [b8ce7dc]
285
+ - Updated dependencies [dad51e5]
286
+ - Updated dependencies [1c9c342]
287
+ - Updated dependencies [787c738]
288
+ - Updated dependencies [8396656]
289
+ - Updated dependencies [dbbd38a]
290
+ - Updated dependencies [8871c14]
291
+ - Updated dependencies [93fe362]
292
+ - Updated dependencies [dfc6975]
293
+ - Updated dependencies [3cf4de0]
294
+ - Updated dependencies [c9dc811]
295
+ - Updated dependencies [144ef9b]
296
+ - Updated dependencies [138ab04]
297
+ - Updated dependencies [a0b9e91]
298
+ - Updated dependencies [99bd015]
299
+ - Updated dependencies [21e4585]
300
+ - @object-ui/types@17.6.0
301
+ - @object-ui/i18n@17.6.0
302
+ - @object-ui/react@17.6.0
303
+ - @object-ui/components@17.6.0
304
+ - @object-ui/core@17.6.0
305
+
306
+ ## 17.5.0
307
+
308
+ ### Minor Changes
309
+
310
+ - 5fac011: Publish `normalizeChartSchema` from the package entry.
311
+
312
+ `normalizeChartSchema` is the single place the author-facing chart schema is translated into the renderer's internal pipeline contract, and `ChartRenderer` calls it on every render. It was not reachable from the package's only entry point, so a consumer that wanted to assert what `AdvancedChartImpl` is actually handed had to restate the translation rather than run it. It is now exported from the entry, along with the `NormalizedChartSchema` type it returns.
313
+
314
+ Additive only: nothing is removed or renamed, and the module was already in the entry's eager import graph via `ChartRenderer`, so this publishes a name rather than shipping new bytes.
315
+
316
+ ### Patch Changes
317
+
318
+ - 5900ac5: Analytics surfaces now run resolved select-option labels through the locale bundle — the chart legend and the related list on one page stop disagreeing
319
+
320
+ A dashboard widget grouped by a `select` field rendered the option's authored English label while the related list beside it rendered the translation. The decisive evidence in objectui#4030 is the stored value `orion`: the chart read `Orion Engineered Carbons`, a string with no resemblance to the value and matching the object's `label` byte for byte. So the analytics path had already RESOLVED the option label — it simply never ran the result through the i18n bundle before display. (`domestic → Domestic` differs from its value by case alone, which is why the first diagnosis, "the report groups by stored value", was wrong.)
321
+
322
+ There is exactly one resolution channel and this change reuses it rather than adding a chart-side dialect: `fieldOptionLabel` from `useObjectLabel`, i.e. `{ns}.fieldOptions.<object>.<field>.<value>` — the convention `@objectstack/spec` names objectui as the reader of, and the one list, form, kanban and record-picker surfaces already translate select options through. The bundle is applied ONCE, at the output of the label net that landed in objectui#4053/#4263, on the shared option list every consumer reads: chart axis and legend, the table/pivot cells of a dotted dimension, that table's CSV export, per-category colours and the declared category order. `@object-ui/core` gains `localizeFieldOptions` (the pure mirror of `translateOptions`), an optional translator on `buildDimensionLabelMap`, and `resolveDimensionFieldMeta` — the same single relationship walk `resolveDimensionFieldOptions` performs, now keeping the object that OWNS the terminal field, because for `crm_account.industry` the bundle key is `crm_account`, not the dataset's base object.
323
+
324
+ Two properties the fix is shaped around. The rows reach this net keyed either way — by stored value when the server did not resolve the dimension, by the English label when it did (ADR-0021) — and the reported screen is the second case, so the map answers to both keys and lands on the same translated display. And identity is untouched: `relabelDimensions` still rewrites display only, so a drilled chart segment clicked as `欧励隆` filters by `orion`, bucket ids and pivot totals keep their raw keys, and an option with no bundle entry (or an `en` console) renders exactly the authored label it renders today.
325
+
326
+ The per-locale work moved from the metadata fetch into the render, so switching language now re-labels in place instead of waiting for a refetch.
327
+
328
+ Not covered, and unchanged here: a LOCAL select dimension on a table/pivot, whose label the server resolves and whose client-side net is deliberately off (objectui#4263), and a dashboard global filter's own field label, which has no object name in its metadata to key a bundle lookup with — tracked on objectui#4030.
329
+
330
+ - 3fc2971: A null-keyed group renders as an explicit bucket instead of silently vanishing from a chart (objectui#4466)
331
+
332
+ `buildChartSeries`' single-dimension branch passed rows through verbatim, so a row whose category VALUE is `null` reached recharts with a null category and drew no mark. The visible outcome was not an empty chart but a quietly wrong one: rows `[{user_id: null, event_count: 51}, {user_id: 'Dev Admin', event_count: 2}]` drew exactly ONE bar — the dominant group, 51 of 53 events, dropped while the y-axis scale still accommodated it, so the chart understated its own data and the axis proved the data had been there. With every group null it drew axes, gridlines and an axis title with zero marks and no empty state, which is the shipped first-boot state of the built-in System Overview board's "Events by User" (every seeded `sys_audit_log` row is written with `user_id = NULL`).
333
+
334
+ The mapping lives in the shared series layer, so dashboard widgets and standalone `ObjectChart` get one answer rather than a per-chart patch in the recharts wrapper. It resolves the two-answers disagreement the card names as well: an empty result set keeps the designed empty state, a non-empty result always draws bars — the null bucket included.
335
+
336
+ `@object-ui/core` gains `NULL_CATEGORY_LABEL` and `ChartSeriesOptions`; `buildChartSeries` and `findChartSeriesRow` each take an optional trailing `options`. Both additive — every existing call site compiles and behaves identically, and a result with no null category is still returned by array identity. The two helpers are a pair on purpose: the caller matches a clicked segment against rows that still carry the raw `null`, so `findChartSeriesRow` reads the bucket label back to that row and the newly-visible bar keeps its drill-through instead of resolving to `-1`.
337
+
338
+ The label goes through the i18n channel (`chart.nullCategory`, en `(None)` / zh `(未指定)`, all ten packs), passed down by the renderer: `@object-ui/core` is React-free and cannot read the locale bundle, so it takes the resolved string the same way `dimensionOptionTranslator` takes a resolver. Its English constant is the floor for a provider-less host, not the mechanism.
339
+
340
+ `hasNoCategoryKey` (framework#4033) is untouched and now documented against this: a row that does not carry the category key AT ALL is a different defect — a dimension grouped by but never projected — and keeps its explanatory placeholder. The bucket deliberately never ADDS the key to such a row, which is what keeps that guard's signal alive. Key absent → the placeholder; key present with a null value → the bucket.
341
+
342
+ - aca27fa: The multi-dimension pivot branch buckets a null first-dimension value instead of dropping its bar (objectui#4497)
343
+
344
+ `buildChartSeries`' pivot branch (2+ dimensions, single measure) bucketed rows by `String(xRaw ?? '')` but wrote the RAW value into the emitted row, so a null first-dimension value produced `{status: null, Low: 3}` and reached recharts with a null category — which draws no mark. Measured at the DOM: a two-group pivot drew ONE bar, and an all-null pivot drew axes and gridlines with zero bar rectangles and no empty state. That is the same mechanism objectui#4466 fixed one branch below, on the branch that card deliberately left pinned as-is until the pivot's own bucketing had been measured.
345
+
346
+ The pivot now maps a null/undefined first-dimension VALUE to the same bucket label the single-dimension branch uses — `ChartSeriesOptions.nullCategoryLabel`, defaulting to `NULL_CATEGORY_LABEL`. One doctrine, one predicate, two call sites; no new export, and every existing call site compiles and behaves identically.
347
+
348
+ The bucket KEY is untouched, which is what keeps this a display fix: `String(xRaw ?? '')` still decides which rows share a bar, so every existing grouping is byte-identical and only the label the bucket carries changes. Rows that lack the category key entirely are still not bucketed — that shape is a dimension grouped by but never projected (framework#4033), a different defect with a different answer.
349
+
350
+ Drill-through needed no change, which was measured rather than assumed: the pivot's emitted rows are AGGREGATED, so they are not index-aligned with `drillRawRows` and the one production caller (`DatasetWidget.handleChartDrill`) already drills by SEARCHING the raw rows through `findChartSeriesRow`. Those raw rows still carry their null, and objectui#4466's label-matching covers the multi-dimension arm as well as the single-dimension one, so the newly-visible bar resolves to the right record. Pinned at both levels so a regression in either half surfaces as the dead click it would be.
351
+
352
+ - 613b167: A dataset dimension on a dotted relationship path now renders its option labels instead of the raw stored enum
353
+
354
+ A `DatasetDimension` whose `field` is a relationship path (`crm_account.industry`) got no select-option resolution at all: the chart plotted `education`, `finance`, `manufacturing` — the database column, unresolved — while the **same underlying field** reached as a **local** dimension rendered `Education`, `Finance`, `Manufacturing` beside it on the same dashboard. Nothing errored, so the widget just quietly showed database enum values to end users; on a non-English deployment those are words that appear nowhere else in the UI, since every form and list shows the translated label.
355
+
356
+ The label lookup read options as `baseObject.fields[<path>]`, which only ever matches the local spelling. For a dotted path the options live on the **related** object, so the lookup missed and the renderer fell through to the stored value.
357
+
358
+ The object-resolution step of that one lookup now walks the path: each segment before the last must be a declared relationship (`lookup` / `master_detail`, target read from `reference` / `reference_to` / `referenceTo` / `reference_to_object`), and the terminal field's options are read off the object that actually owns it. This is the same lookup for both spellings rather than a dotted-path variant beside it — a single-segment path never enters the walk and resolves exactly as before, so the local and joined paths cannot drift apart. Multi-hop paths (`crm_account.owner.department`) resolve too, which is the shape the dataset designer already emits.
359
+
360
+ Hops ride the caller's existing `GET /meta/object/:name` channel — the same authenticated read that fetched the base object — so no new fetch layer is introduced, and objects are fetched once per resolution even when several dimensions share a prefix. Every failure stays best-effort: a segment that is not a relationship, a target that cannot be loaded, or a terminal field with no options yields no mapping and the raw value survives, exactly as it does today.
361
+
362
+ Applies to both surfaces that carried this lookup: dashboard dataset widgets (`DatasetWidget`) and the chart view's dataset path (`ObjectChart`).
363
+
364
+ Scope: this ends at "the label is in hand". Whether that label then passes through the i18n bundle is a separate gap tracked upstream as objectstack#5076.
365
+
366
+ - 0b49d60: Analytics: `ObjectChart` consumes the shared label-net helpers instead of a third copy
367
+
368
+ objectui#4389 (PR #4404) named two copies of the analytics label-net glue — the dashboard's `DatasetWidget` and plugin-report's dataset block — and retired both into `@object-ui/core` + `@object-ui/react`. There was a THIRD, which that card did not name and its PR deliberately left out of scope: `packages/plugin-charts/src/ObjectChart.tsx` carried its own `translatorFor` closure, its own `buildDimensionLabelMap` loop, and its own base-object-read-then-walk composition. The `translatorFor` copy was logically identical to the two that were deleted, down to the comment explaining the binding.
369
+
370
+ `ObjectChart` now calls core's `dimensionOptionTranslator`, `deriveDimensionLabelMaps` and `loadDimensionFieldMeta` directly. Nothing about what a label IS changes — those helpers are the same code the two retired copies were rewritten onto, so the part that was genuinely duplicated three times is now written once.
371
+
372
+ Behaviour is unchanged by construction: same two metadata reads in the same order on the dataset path, same one read on the aggregate path, same best-effort fallback (an unresolvable path yields no entry and the raw value survives), same locale-applying memo boundary. `plugin-charts`' 22 test files / 170 assertions pass unchanged and their files are byte-identical to before, which is the acceptance evidence for a pure swap.
373
+
374
+ The card's second, optional step — moving the DATASET path's metadata read onto `@object-ui/react`'s `useDatasetDimensionMeta` — was attempted and declined on measurement; the shape blocker is recorded on objectui#4405 and in the PR. The two bug-fix properties the family exists to state (the read rides the host's authenticated `apiFetch`, objectui#4121; the fetched metadata stays locale-free, objectui#4030 / PR #4324) therefore remain stated locally in this file, exactly as before, and are undisturbed by this change.
375
+
376
+ - bcd3e02: `ObjectChart`'s category option-color / dimension-label probe now rides the host's
377
+ authenticated fetch (`SchemaRendererContext.apiFetch`) instead of the bare global
378
+ `fetch`.
379
+
380
+ Both metadata reads the effect makes — `GET /api/v1/meta/dataset/{dataset}` and
381
+ `GET /api/v1/meta/object/{object}` — went out on the global `fetch`, so in a hosted
382
+ console they skipped whatever the host supplies on that channel (Authorization /
383
+ tenant headers, base-URL rewrite, draft-preview params). A bearer-token session
384
+ carries its credential in a header rather than a cookie, so `credentials: 'include'`
385
+ alone left these two reads unauthenticated. The effect is best-effort and swallows
386
+ every failure, which made the symptom silent: semantic option colors and dataset
387
+ dimension labels simply never applied, and the chart fell back to the positional
388
+ theme palette and raw stored values.
389
+
390
+ Standalone embeds are unaffected — with no provider (or a provider that supplies no
391
+ `apiFetch`) the probe still uses the global `fetch`, the same documented fallback
392
+ `useRecordEditable` and `provider: 'api'` view sources use.
393
+
394
+ - Updated dependencies [0e67b53]
395
+ - Updated dependencies [ceccdcf]
396
+ - Updated dependencies [d6e5124]
397
+ - Updated dependencies [debad27]
398
+ - Updated dependencies [dc2aa3e]
399
+ - Updated dependencies [ee66e2e]
400
+ - Updated dependencies [ee26e65]
401
+ - Updated dependencies [5900ac5]
402
+ - Updated dependencies [932cbcd]
403
+ - Updated dependencies [734d186]
404
+ - Updated dependencies [f650253]
405
+ - Updated dependencies [3d9769a]
406
+ - Updated dependencies [8f85f8b]
407
+ - Updated dependencies [d0c3b26]
408
+ - Updated dependencies [3fc2971]
409
+ - Updated dependencies [aca27fa]
410
+ - Updated dependencies [dde7283]
411
+ - Updated dependencies [f7c6430]
412
+ - Updated dependencies [4dadf0d]
413
+ - Updated dependencies [ae10a01]
414
+ - Updated dependencies [92876f0]
415
+ - Updated dependencies [f279deb]
416
+ - Updated dependencies [4b70d28]
417
+ - Updated dependencies [eb7f586]
418
+ - Updated dependencies [e901131]
419
+ - Updated dependencies [d9d3463]
420
+ - Updated dependencies [2a40f69]
421
+ - Updated dependencies [bec3e14]
422
+ - Updated dependencies [613b167]
423
+ - Updated dependencies [b4d3c22]
424
+ - Updated dependencies [1f9b905]
425
+ - Updated dependencies [cb13400]
426
+ - Updated dependencies [828549a]
427
+ - Updated dependencies [e1ade8f]
428
+ - Updated dependencies [bc64bfe]
429
+ - Updated dependencies [abb0f81]
430
+ - Updated dependencies [38ab505]
431
+ - Updated dependencies [3e19fe7]
432
+ - Updated dependencies [bb58d1d]
433
+ - Updated dependencies [5cc847c]
434
+ - Updated dependencies [fa21254]
435
+ - Updated dependencies [33c32bf]
436
+ - Updated dependencies [66fb4fa]
437
+ - Updated dependencies [b953a97]
438
+ - Updated dependencies [d7f3e30]
439
+ - Updated dependencies [6d641c9]
440
+ - Updated dependencies [7e4f0e5]
441
+ - Updated dependencies [a84385b]
442
+ - Updated dependencies [45e1949]
443
+ - Updated dependencies [92250d6]
444
+ - Updated dependencies [c1d939f]
445
+ - Updated dependencies [58bebf6]
446
+ - Updated dependencies [405e808]
447
+ - Updated dependencies [49ae9f4]
448
+ - Updated dependencies [a3ae404]
449
+ - Updated dependencies [bfdf3d4]
450
+ - Updated dependencies [bb68488]
451
+ - Updated dependencies [c0f9a4b]
452
+ - Updated dependencies [b1e42d0]
453
+ - Updated dependencies [2459a3e]
454
+ - Updated dependencies [ac853ce]
455
+ - Updated dependencies [fa51109]
456
+ - Updated dependencies [d6aa172]
457
+ - Updated dependencies [fe52a04]
458
+ - Updated dependencies [d46f9b8]
459
+ - Updated dependencies [3f5f87c]
460
+ - Updated dependencies [2fea4d2]
461
+ - Updated dependencies [f5e1143]
462
+ - Updated dependencies [7f1cb33]
463
+ - Updated dependencies [f148a64]
464
+ - Updated dependencies [bb68488]
465
+ - Updated dependencies [2e3b0c0]
466
+ - Updated dependencies [9461dd3]
467
+ - Updated dependencies [78fa331]
468
+ - Updated dependencies [47f551b]
469
+ - Updated dependencies [31ab1ac]
470
+ - Updated dependencies [0082db8]
471
+ - Updated dependencies [ab04728]
472
+ - Updated dependencies [5bf09fd]
473
+ - Updated dependencies [06915b0]
474
+ - Updated dependencies [ff84b05]
475
+ - @object-ui/i18n@17.5.0
476
+ - @object-ui/react@17.5.0
477
+ - @object-ui/components@17.5.0
478
+ - @object-ui/core@17.5.0
479
+ - @object-ui/types@17.5.0
480
+
481
+ ## 17.4.0
482
+
483
+ ### Patch Changes
484
+
485
+ - a7e39a8: `ChartContainer`'s min-size fallback survives a consumer-supplied `style` (objectstack#7026)
486
+
487
+ The container wrote `style={{ minHeight: 280, minWidth: 0, ...props.style }}` and
488
+ then spread `{...props}` on the line BELOW it. `props` is the rest of
489
+ `ComponentProps< "div" >` — only `id`, `className`, `children`, `config` and
490
+ `disableSettleRemount` are destructured out of it — so it still carried the
491
+ consumer's `style`, and a later JSX attribute of the same name replaces an earlier
492
+ one outright. Any caller that passed a `style` therefore replaced the whole object:
493
+ both `minHeight` and `minWidth` vanished, and the `...props.style` merge written
494
+ inside it never executed even once. It was dead code that read, to anyone auditing
495
+ the file, as if the fallback were guaranteed.
496
+
497
+ That fallback is not decorative. It exists so Recharts' `ResponsiveContainer`
498
+ always has a non-zero box to measure: a dashboard widget that overrides the
499
+ container's `h-[350px]` class and wraps the chart in flex/grid without an explicit
500
+ child height leaves the box at 0, Recharts measures `width/height = -1`, and the
501
+ chart renders invisibly — the exact failure the guard was added for.
502
+
503
+ `style` is now destructured out of the rest props and merged explicitly, so which
504
+ side wins is stated in code instead of being decided by JSX attribute order, and
505
+ `{...props}` can no longer reach `style` at all.
506
+
507
+ Precedence: **an author's explicit size wins.** Simply spreading `{...props}`
508
+ first and merging unconditionally would have traded this bug for its mirror image
509
+ — `minHeight: 280` injected next to an authored `height: 100` floors that 100 to
510
+ 280, silently overriding the author. So each half of the fallback applies only
511
+ when the consumer style declares neither of its own keys: `height`/`minHeight`
512
+ gate the height half, `width`/`minWidth` gate the width half, and a key present
513
+ but set to `undefined`/`null` counts as not declared. Every other consumer style
514
+ key passes through untouched.
515
+
516
+ Behaviour change surface, deliberately narrow. A caller that supplies no `style`
517
+ is byte-for-byte unchanged. A caller whose `style` declares a height — which today
518
+ is the only shape in the tree, `AdvancedChartImpl`'s `containerProps` forwarding
519
+ `ChartConfig.height` — keeps exactly the height it authored, also unchanged, and
520
+ additionally regains the `minWidth: 0` half. What changes is the case the issue
521
+ was filed for: a `style` carrying no size key (a margin, a padding, an
522
+ aspect-ratio, any future container-level presentation prop routed through the same
523
+ `containerProps` path) now keeps the min-size fallback instead of silently
524
+ stripping it.
525
+
526
+ Pinned in both directions, since a one-sided pin would have been satisfied by the
527
+ mirror-image fix: a non-size `style` keeps `min-height: 280` / `min-width: 0`
528
+ (red before this change), and an explicit `height: 100` renders as 100 with no
529
+ `min-height` floor (red under the unconditional-merge alternative).
530
+
531
+ - 4bc6c23: Converge dashboard widget `compareTo` on the executor's `{ kind, dimension? }` contract, and make the dataset path actually render a comparison
532
+
533
+ `CompareToConfig` was a three-branch union (`'previousPeriod' | 'previousYear' | { offset }`). `@objectstack/spec` collapsed it to the shape the analytics executor already implements — `DatasetCompareTo`, a plain strict object `{ kind: 'previousPeriod' | 'previousYear'; dimension?: string }` (objectstack#5011) — so this renderer now reads that one shape:
534
+
535
+ - `shiftFilterByCompareTo` / `compareToTrendLabelKey` dispatch on `compareTo.kind`. The `{ offset }` duration shift is gone: `{ offset: '1y' }` is `kind: 'previousYear'`, while `'7d'` / `'1M'` have no faithful target and are restated by the author on the widget's own `filter` plus `kind: 'previousPeriod'`. No trend label key is retired — the offset arm resolved to `vsPreviousPeriod`, which survives as the `previousPeriod` fallback.
536
+ - `DatasetWidget` no longer discards part of `compareTo`. It used to forward only the object form because the two string forms had no meaning downstream; with one shape there is nothing to discard, and a stale string is now invalid metadata rejected where it is authored rather than silently reinterpreted here.
537
+ - **The comparison now actually runs on the dataset path.** A widget states its window in its own `filter` (a date macro, or the dashboard date-range filter merged in), but the executor shifts a `timeDimensions` entry carrying a `dateRange` — so a dataset widget asking for a comparison got "compareTo needs a dated window to shift" and rendered none. When (and only when) a comparison is requested, the resolved filter's bounded date windows are lowered into `selection.timeDimensions[].dateRange` and moved out of `runtimeFilter` (a copy left behind would intersect the shifted window with the current one and empty every comparison column). Which dimension gets shifted stays the executor's decision: every window found is lowered under the name the author wrote, and zero or two candidates surfaces the executor's own error, listing them.
538
+ - The `<measure>__compare` columns that come back are now shown: a delta + window label on KPI widgets, a comparison column on tables, and a `variant: 'comparison'` overlay series on charts — the same treatment and the same `dashboard.trend.*` labels the inline object-provider widgets already use.
539
+
540
+ - 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
541
+ `list-view` — and `element:record_picker` stops discarding `view`
542
+ (objectstack#6953).
543
+
544
+ objectstack#5576 wired the spec's per-element data binding
545
+ (`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
546
+ the same declaration inert on every other page component. Two gaps remained, and
547
+ both were silent:
548
+
549
+ - **`element:record_picker` read four of the five keys and dropped `view`.** So
550
+ `dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
551
+ built a picker over EVERY account instead of the rows the saved view selects.
552
+ Nothing threw and nothing rendered an error; the option list was simply wider
553
+ than what was authored, which also means a user could select a record the page
554
+ said was out of scope.
555
+ - **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
556
+ `object-chart` / `object-metric` / `record:related_list` read none of it.**
557
+ Each gates its fetch on its own `objectName`, and nothing mapped
558
+ `dataSource.object` onto it, so a page written the way the spec documents
559
+ rendered an empty grid / a field-less form / a board with no cards / an empty
560
+ month / an empty chart / a static metric number — with no request and no
561
+ diagnostic anywhere. Spec-valid metadata rendering nothing is the
562
+ objectstack#4413 shape.
563
+
564
+ Composition follows objectstack#5576's landed semantics unchanged on every block:
565
+ a named saved view supplies the baseline, a key written on the component itself
566
+ overrides it, an explicit binding key overrides both, `filter` AND-combines
567
+ ("additional filter criteria" — a binding can narrow a view, never widen it), and
568
+ a `view` name that does not resolve renders a configuration error instead of
569
+ degrading to the object's full scope.
570
+
571
+ - `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
572
+ and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
573
+ block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
574
+ for the two non-final states. One precedence table for all blocks rather than
575
+ one copy per block — that copy is how "additional filter criteria" would have
576
+ become two dialects.
577
+ - A mapping names **only** keys its block genuinely reads. A composed value
578
+ written onto a key the block ignores would be accepted and dropped, which is
579
+ the defect being removed, one layer deeper — so a kanban's swimlane `columns`
580
+ never receive a view's field list, and a block with no row cap leaves `limit`
581
+ unmapped. The per-block coverage table, including two residual gaps that are
582
+ named rather than papered over, is in `content/docs/guide/data-source.md`.
583
+
584
+ No behaviour changes for a block that carries no `dataSource`: the binding-free
585
+ path returns the schema by reference, so nothing remounts and nothing refetches.
586
+
587
+ - Updated dependencies [794c497]
588
+ - Updated dependencies [993336f]
589
+ - Updated dependencies [f0a625a]
590
+ - Updated dependencies [b5980f4]
591
+ - Updated dependencies [8aad9fd]
592
+ - Updated dependencies [6719877]
593
+ - Updated dependencies [56ff091]
594
+ - Updated dependencies [7864f03]
595
+ - Updated dependencies [0cbdca8]
596
+ - Updated dependencies [d229dfa]
597
+ - Updated dependencies [ecae400]
598
+ - Updated dependencies [4bc6c23]
599
+ - Updated dependencies [d3e738a]
600
+ - Updated dependencies [c3b01a7]
601
+ - Updated dependencies [f5f8744]
602
+ - Updated dependencies [7ed3360]
603
+ - Updated dependencies [69becd2]
604
+ - Updated dependencies [5e52495]
605
+ - Updated dependencies [0fa5e4d]
606
+ - Updated dependencies [b750823]
607
+ - Updated dependencies [5bfaabd]
608
+ - Updated dependencies [e06810e]
609
+ - Updated dependencies [ab3ad4f]
610
+ - Updated dependencies [c2fd122]
611
+ - Updated dependencies [ac2139c]
612
+ - Updated dependencies [b14ab3a]
613
+ - Updated dependencies [e24d767]
614
+ - Updated dependencies [8c60819]
615
+ - Updated dependencies [aca561a]
616
+ - Updated dependencies [e64a52e]
617
+ - Updated dependencies [844d17f]
618
+ - Updated dependencies [48132f7]
619
+ - Updated dependencies [4dcd52a]
620
+ - Updated dependencies [42ae5c6]
621
+ - Updated dependencies [0ef9dfd]
622
+ - Updated dependencies [1d723e3]
623
+ - Updated dependencies [0109f54]
624
+ - Updated dependencies [7e5bb5d]
625
+ - Updated dependencies [fbc23e0]
626
+ - Updated dependencies [6d762da]
627
+ - Updated dependencies [e6fdbdc]
628
+ - Updated dependencies [54233b1]
629
+ - Updated dependencies [f9faa7d]
630
+ - Updated dependencies [97b63d7]
631
+ - Updated dependencies [6bb454a]
632
+ - Updated dependencies [523be48]
633
+ - Updated dependencies [7e2b7e9]
634
+ - Updated dependencies [33526fd]
635
+ - Updated dependencies [32413ec]
636
+ - Updated dependencies [c1e1e6b]
637
+ - @object-ui/components@17.4.0
638
+ - @object-ui/react@17.4.0
639
+ - @object-ui/core@17.4.0
640
+ - @object-ui/i18n@17.4.0
641
+ - @object-ui/types@17.4.0
642
+
3
643
  ## 17.3.0
4
644
 
5
645
  ### Minor Changes
@@ -872,6 +1512,7 @@
872
1512
  project `health`) painted its categories from the generic `--chart-1..5`
873
1513
  palette — the same gap the chart view (`object-chart`) had before #1932. It now
874
1514
  resolves the dimension field's option colors (using the dataset's base `object`
1515
+
875
1516
  - dimension→field map the query already returns) and threads them to the
876
1517
  renderer as a per-category `categoryColors` map, so health green/red/yellow
877
1518
  paints semantically.
@@ -1078,6 +1719,7 @@
1078
1719
  - 991b62d: Add `compareTo` field to dashboard widgets for period-over-period
1079
1720
  comparison. Supports `'previousPeriod'`, `'previousYear'`, and
1080
1721
  `{ offset: '7d' | '4w' | '1M' | '1y' }`.
1722
+
1081
1723
  - **Metric / gauge widgets** now compute a delta percentage when `compareTo`
1082
1724
  is set and surface it as a derived `trend` (auto-labelled via
1083
1725
  `dashboard.trend.vsLast*` i18n keys sniffed from the filter macros).
@@ -1562,6 +2204,7 @@
1562
2204
  Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined `require("react")` / `require("react-dom")` calls that cause `Calling \`require\` for "react" in an environment that doesn't expose the \`require\` function` runtime errors when consumer apps re-bundle the published dist.
1563
2205
 
1564
2206
  Specifically fixes:
2207
+
1565
2208
  - `@object-ui/plugin-dashboard` no longer inlines `react-grid-layout` (and its transitive `react-draggable` / `react-resizable` CJS bundles). `react-grid-layout` is now declared as a peer dependency so consumers install a single ESM-friendly copy.
1566
2209
  - `@object-ui/components`, `@object-ui/plugin-calendar`, `@object-ui/plugin-charts`, `@object-ui/plugin-designer` no longer inline `react-i18next` / `i18next` / `use-sync-external-store` CJS shims.
1567
2210
  - All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.
@@ -1795,6 +2438,7 @@
1795
2438
  - New plugin-object and ObjectQL SDK updates
1796
2439
 
1797
2440
  **Added:**
2441
+
1798
2442
  - New Plugin: @object-ui/plugin-object - ObjectQL plugin for automatic table and form generation
1799
2443
  - ObjectTable: Auto-generates tables from ObjectQL object schemas
1800
2444
  - ObjectForm: Auto-generates forms from ObjectQL object schemas with create/edit/view modes
@@ -1803,6 +2447,7 @@
1803
2447
  - ObjectQL Integration: Enhanced ObjectQLDataSource with getObjectSchema() method using MetadataApiClient
1804
2448
 
1805
2449
  **Changed:**
2450
+
1806
2451
  - Updated @objectql/sdk from ^1.8.3 to ^1.9.1
1807
2452
  - Updated @objectql/types from ^1.8.3 to ^1.9.1
1808
2453
 
@@ -1819,6 +2464,7 @@
1819
2464
  - Patch release: Add automated changeset workflow and CI/CD improvements
1820
2465
 
1821
2466
  This release includes infrastructure improvements:
2467
+
1822
2468
  - Added changeset-based version management
1823
2469
  - Enhanced CI/CD workflows with GitHub Actions
1824
2470
  - Improved documentation for contributing and releasing