@object-ui/plugin-report 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,695 @@
1
1
  # @object-ui/plugin-report
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a945271: `DatasetReportRenderer` no longer reads `filter` as an alias for `runtimeFilter`.
8
+
9
+ `filter` is not an authorable key. `ReportSchema` in `@objectstack/spec` (17.0.0)
10
+ is `z.core.$strict` and rejects it outright, going out of its way to name the
11
+ replacement: ``Unrecognized key(s) on this report: `filter`. Did you mean
12
+ `filter` → `runtimeFilter`?`` The renderer accepted the rejected spelling anyway,
13
+ at two sites — the report-level `report.runtimeFilter ?? report.filter` and the
14
+ per-joined-block `block.runtimeFilter ?? block.filter`. Metadata that could not
15
+ be authored through the validated path still rendered when it arrived by some
16
+ other route, so the strictness the spec promises was not the strictness the
17
+ runtime enforced. It matters most for AI-authored metadata: a generator emitting
18
+ `filter` got a working report from this renderer and a hard validation failure
19
+ from anything that parsed the document first, and which one the author met was a
20
+ function of which path the document travelled.
21
+
22
+ **Behaviour change — read this if you store report documents.** A stored document
23
+ still carrying `filter` no longer has that filter applied: it renders
24
+ **unfiltered**, showing rows the report was scoped to exclude. Server-side
25
+ permissions are unaffected and still apply, so this is not an access-control
26
+ change — it is a report showing more rows than its author intended. Scored
27
+ `minor` per this repo's version policy (AGENTS.md §版本号策略), which reserves
28
+ `major` for the synchronized `@objectstack` major bump; the narrowing is
29
+ described here rather than encoded in the number.
30
+
31
+ Because rendering unfiltered *in silence* would be worse than an error, the
32
+ dropped key is not silent. When a document carrying `filter` reaches the
33
+ renderer, a dev-mode warning fires once per offending report or block, naming the
34
+ key, quoting the spec's own rename hint verbatim (so an author meets one message
35
+ rather than two dialects of one message), and stating that no filter was applied.
36
+ It is a no-op under `NODE_ENV=production` and changes no types.
37
+
38
+ Fix your documents by renaming `filter` to `runtimeFilter` — the key the schema
39
+ actually declares, and the one the suggestion has always pointed at. Reports that
40
+ already author `runtimeFilter`, and hosts that pass the `runtimeFilter` prop, are
41
+ entirely unaffected.
42
+
43
+ ### Patch Changes
44
+
45
+ - cb5a7de: A report's embedded chart now paints its category dimension's own option colours and renders ordered-sequence charts (funnel/pyramid) in the field's declared picklist order — the same two derivations a dashboard chart has always gotten for the identical dimension (objectui#4906).
46
+
47
+ `DatasetReportChart` (`DatasetReportRenderer.tsx`) resolved its dimension's option **labels** but called neither `buildOptionColorMap` nor `buildCategoryOrder` — the two `@object-ui/core` helpers `DatasetWidget` (plugin-dashboard) already runs off the same resolved field metadata. The chart forwarded only an author-supplied `colors` record (objectui#4877); with none authored it fell back to the positional palette, and a funnel's stages sorted by value instead of the declared pipeline.
48
+
49
+ This is convergence onto an already-ruled behavior, not new capability: the report path now runs the identical `useDatasetDimensionMeta` → `localizeFieldOptions` → `buildOptionColorMap`/`buildCategoryOrder` chain the dashboard widget uses (framework#3588's declared-picklist-order ruling), reused rather than re-derived.
50
+
51
+ **This visibly changes rendering for an existing report** that groups by a select/lookup dimension carrying option colours, or is declared on an ordered field:
52
+
53
+ - a chart with no authored `colors` now paints each category in that dimension's own option colour (e.g. a `health` dimension now paints its own green/amber/red) instead of the renderer's positional palette;
54
+ - a `funnel`/`pyramid` chart now orders its stages by the field's declared picklist order instead of sorting by value.
55
+
56
+ Precedence is unchanged and preserved: an authored `colors` record (objectui#4877) still wins over the derived per-category map, merged UNDER it exactly as the dashboard already does — an author's explicit colour for a category is never overridden by the field's own.
57
+ - 0237208: docs(plugin-report): teach the dataset-bound report as the sole authoring shape
58
+
59
+ `packages/plugin-report/README.md` and `content/docs/plugins/plugin-report.mdx`
60
+ still taught the pre-9.0 query form (`objectName` + column-definition `columns` +
61
+ `groupingsDown` / `groupingsAcross`) as the live way to write a report, and named
62
+ three renderers plus `applyInMemoryAggregation` that were removed at the ADR-0021
63
+ cutover. The current `ReportSchema` is strict and rejects that form outright, so
64
+ every copyable example on both pages now authors the dataset-bound shape
65
+ (`dataset` + `rows` / `columns` / `values`, `runtimeFilter`, `order`, `drilldown`,
66
+ `chart`) through `defineReport`. Stored pre-9.0 documents keep one clearly
67
+ labelled migration-only section describing the lossy bridge that renders them and
68
+ the key-by-key direction of migration.
69
+ - 82a9417: docs(plugin-report): rewrite the README export snippets against the real export signatures
70
+
71
+ The `### Export` and `### Live Export` snippets called every export function with
72
+ the wrong arity or argument order. Every name was real, so the name-set check
73
+ could not see it — only the calls were wrong:
74
+
75
+ - The six format exporters are `(report: ReportComponentSchema, data: any[], config?: ReportExportConfig): void`.
76
+ The README called them `(data, filename)`, so a filename string landed in the
77
+ `data` slot the engine iterates as rows, and there is no filename parameter at
78
+ all — the download name comes from `config.filename`, else `report.title`. All
79
+ five are synchronous `void`, so the snippet's `await` was inert.
80
+ `exportReport` takes the format **first**, which no snippet showed.
81
+ - `exportWithLiveData(report, options)` requires `dataSource` and `resource` in
82
+ `LiveExportOptions`; the README passed only `{ format: 'pdf' }`.
83
+ - `exportExcelWithFormulas(report, data, options)` takes three parameters; the
84
+ README passed two, and spelled the column key `field` where `ExcelColumnConfig`
85
+ has the required `name` and `header`. Formula templates use the `{ROW}`
86
+ placeholder, which the old `SUM(B2:B100)` never exercised.
87
+
88
+ The rewritten blocks are the exporters' own (correct) JSDoc examples, and each
89
+ one now compiles against the package's built `dist/index.d.ts`. Docs only — no
90
+ API, export surface or runtime behaviour changed.
91
+ - f331f5a: Docs only: `packages/plugin-report/README.md` no longer teaches three exports the
92
+ package does not have (objectui#5016). Each was judged individually against the
93
+ entry module's export surface, and all three turned out to be **survivors of the
94
+ 9.0 cutover** rather than renames — `CHANGELOG.md` records `ReportBuilder`,
95
+ `ScheduleConfig` and the drill helpers as removed with the pre-9.0 query-form
96
+ renderers, and the README kept teaching them afterwards:
97
+
98
+ - **`ReportBuilder`** — taught as the main editor component. Removed; there is no
99
+ authoring component in this package. The Quick Start now teaches the real ones,
100
+ with their real signatures: `ReportRenderer` (the dispatcher, which takes the
101
+ report as `schema`), `DatasetReportRenderer` (which takes it as `report`), and
102
+ `ReportViewer` — whose props are `{ schema, onRefresh }`, so the old
103
+ `< ReportViewer report={…} showToolbar />` would not have compiled either.
104
+ `report` / `showToolbar` are keys inside a `ReportViewerSchema`.
105
+ - **`registerDrillHandler(actionRunner, …)`** — taught as the drill registration
106
+ call. Removed, and its mechanism no longer exists: drill-down is now a host
107
+ callback, `onDrill?: (args: DatasetDrillArgs) => void`, and the host owns
108
+ navigation because the renderer only knows dimension names (ADR-0021 D2). The
109
+ section is rewritten around what a click actually emits, including why
110
+ `objectFilter` (raw stored values) is what filters select/lookup dimensions
111
+ correctly where a display-label `groupKey` would not.
112
+ - **`ScheduleConfig`** — taught as a configuration component. Removed. A schedule
113
+ is *data* on the report schema: `ReportComponentSchema.schedule`, typed
114
+ `ReportScheduleConfig`. Both types live in `@object-ui/types` and are not
115
+ re-exported here, so the corrected snippet imports them from there —
116
+ `createScheduleTrigger` is the real export, and its real signature takes
117
+ `(report, dataSource, resource, onComplete)` and returns
118
+ `() => Promise< LiveExportResult[] >`, not the single callback the old snippet
119
+ passed.
120
+
121
+ Two further leftovers of the same removal are corrected: the stale survivor
122
+ sentence that listed `ReportBuilder` as still available, and the schema-driven
123
+ example's `"type": "report-builder"`, which no component registers — the
124
+ registered types are `report`, `spec-report` and `report-viewer`, now listed
125
+ explicitly. The two feature bullets asserting the removed mechanisms (a
126
+ `useReportData()` query pipeline, an `ActionRunner`-dispatched `drill` action)
127
+ state the dataset-bound path instead.
128
+
129
+ No code, types or runtime behaviour change — the diff is one README and this
130
+ changeset. The correction reaches npm with the package's next publish, which is
131
+ why it declares a patch: `README.md` is in the package's published `files`.
132
+ - 5ffcc14: fix(plugin-report): forward the chart chrome and series presentation `ReportChartSchema` declares (objectui#4877)
133
+
134
+ A report's embedded chart forwarded exactly six keys to the registered chart
135
+ component — `chartType`, `data`, `height`, `isAnimationActive`, `series`,
136
+ `xAxisKey`. Everything else `ReportChartSchema` declares as authorable never
137
+ left the report renderer, so it was inert metadata: the author writes it, the
138
+ schema accepts it, nothing reads it.
139
+
140
+ `showLegend` was the sharpest case because dropping it does not merely ignore
141
+ the author, it INVERTS them: `AdvancedChartImpl` computes
142
+ `legendVisible = showLegend !== false`, so an absent value means the legend is
143
+ on and an explicit `showLegend: false` still drew one.
144
+
145
+ Now lowered, under objectui#4229's ruled data/presentation split:
146
+
147
+ - chrome — `showLegend`, `showDataLabels`, `colors` (both the positional-palette
148
+ array and the per-category record), `subtitle`, `description`, `annotations`,
149
+ `interaction`, `height`;
150
+ - per-series presentation — `color`, `stack`, `type`, `yAxis`, `dashArray`,
151
+ `opacity`, `variant`, matched by `series[].name` so series MEMBERSHIP stays
152
+ with the dataset.
153
+
154
+ `title` is deliberately not forwarded: the report renderer paints it as its own
155
+ heading above the plot, and forwarding it would draw a second one inside the
156
+ chart's frame. `aria` is not lowered either — nothing on this path reads it
157
+ (`AdvancedChartImpl` has no `aria` prop, and this renderer hands the component a
158
+ schema directly rather than through `SchemaRenderer`'s flat ARIA injection), so
159
+ forwarding it would move declared-but-unread one layer down.
160
+
161
+ The two helpers (`chartConfigPresentation`, `mergeAuthoredPresentation`) moved
162
+ from `plugin-dashboard`'s `DatasetWidget` to `@object-ui/core` beside
163
+ `buildChartSeries`, the derivation they merge onto, so both surfaces lower one
164
+ vocabulary once instead of keeping a second copy (the duplication objectui#4389
165
+ filed as a defect). `@object-ui/core` additionally exports `mergeAuthoredSeries`
166
+ — the series merge alone — for a surface whose axes are bare dimension/measure
167
+ NAME strings rather than spec `ChartAxis` objects, which is what a report chart
168
+ declares. `DatasetWidget` re-exports both names, so its public surface and its
169
+ rendering are unchanged.
170
+ - 6bb39c4: 报表内嵌图表的度量显示名按三级回落解析,不再直接印原始 `name`
171
+
172
+ 数据集绑定的 report chart 此前把度量原样交给图表组件(`series: [{ dataKey }]`,无 label),
173
+ 于是图例、标记 tooltip 与单值卡片的说明文字都落回 dataKey——在全中文控制台上打出
174
+ `potential_upside_tons`,而同一张报表下方的汇总表、以及绑定同一数据集的 dashboard 图表
175
+ 都能正确解析出授权 `label`。`ReportChartSchema` 自 rc.1 起声明的 `series[].label` 也从未
176
+ 被读取,作者因此没有任何可授权的手段控制这个字符串。
177
+
178
+ 现按三级回落解析,与汇总表和 dashboard 的既有口径对齐:
179
+
180
+ 1. `chart.series[]` 中 `name` 命中该度量的条目的 `label`(spec 的 `I18nLabel`,按控制台
181
+ 语言解析;同名重复以第一条为准);
182
+ 2. 绑定数据集的度量 `label`(结果字段的 `label`,即汇总表表头一直在读的同一个值);
183
+ 3. 度量 `name` 兜底。
184
+
185
+ 图例与 tooltip 同源同修:`ChartRenderer` 把 series 的 `label` 写进 `config[dataKey].label`,
186
+ 三处读的是同一个输入。单值族(`metric`/`kpi`/`gauge`)的说明文字与系列图共用这一次解析。
187
+ - 5ffcc14: fix(plugin-report): route a report's embedded chart through `buildChartSeries` so a NULL category is bucketed (objectui#4878)
188
+
189
+ `DatasetReportChart` built its rows as `relabelDimensions(state.rows, …)` and
190
+ handed them to the registered chart component verbatim. Nothing on that path
191
+ bucketed a null dimension value, so a report chart passed the renderer a null
192
+ category — the exact input objectui#4466 measured as drawing **no mark at all**.
193
+ The cost is not an empty chart but a quietly wrong one: the null group vanishes
194
+ while the y-axis scale still accommodates it, so the chart reads as valid data.
195
+
196
+ The dashboard and chart-view surfaces never had the defect because they route
197
+ through `buildChartSeries` (`@object-ui/core`), where the whole null-category
198
+ family was fixed. The report chart now routes through it too, so those
199
+ properties are INHERITED rather than re-derived on a third surface:
200
+
201
+ - the null bucket itself (objectui#4466);
202
+ - its label read from the locale bundle at the call site — `@object-ui/core` is
203
+ React-free, so a zh console would otherwise draw the bar and label it `(None)`
204
+ (objectui#4500);
205
+ - bucket IDENTITY separate from the bucket label, so a stored value that
206
+ literally spells `(None)` stays a different group (objectui#4508).
207
+
208
+ objectui#4020's three-level measure display name still outranks the label the
209
+ derivation assigns, including for an `{ en, 'zh-CN' }` label record: core holds
210
+ no i18n provider and picks first-string-wins, which is exactly the defect class
211
+ #4020 closed.
212
+
213
+ A report whose chart has no null group is unchanged, byte for byte.
214
+ - Updated dependencies [88085e3]
215
+ - Updated dependencies [69251bf]
216
+ - Updated dependencies [57e668f]
217
+ - Updated dependencies [516663d]
218
+ - Updated dependencies [41ac1b7]
219
+ - Updated dependencies [1eaf0a1]
220
+ - Updated dependencies [a09bc33]
221
+ - Updated dependencies [feb6b16]
222
+ - Updated dependencies [460c4d0]
223
+ - Updated dependencies [0ae27f7]
224
+ - Updated dependencies [9aecabe]
225
+ - Updated dependencies [2533ec5]
226
+ - Updated dependencies [78c0f9a]
227
+ - Updated dependencies [bbe8b86]
228
+ - Updated dependencies [8477be5]
229
+ - Updated dependencies [279fb13]
230
+ - Updated dependencies [2e82ab2]
231
+ - Updated dependencies [ad07b65]
232
+ - Updated dependencies [41f498b]
233
+ - Updated dependencies [ef0d150]
234
+ - Updated dependencies [f34226e]
235
+ - Updated dependencies [564b605]
236
+ - Updated dependencies [e1d4251]
237
+ - Updated dependencies [40d3a33]
238
+ - Updated dependencies [8b9dc62]
239
+ - Updated dependencies [1184192]
240
+ - Updated dependencies [a2a9747]
241
+ - Updated dependencies [65e88e6]
242
+ - Updated dependencies [a1609a6]
243
+ - Updated dependencies [53f23bc]
244
+ - Updated dependencies [c4533dc]
245
+ - Updated dependencies [be60815]
246
+ - Updated dependencies [37f6844]
247
+ - Updated dependencies [93de4f6]
248
+ - Updated dependencies [2b50261]
249
+ - Updated dependencies [384f30d]
250
+ - Updated dependencies [ac600e5]
251
+ - Updated dependencies [97fba31]
252
+ - Updated dependencies [232f61a]
253
+ - Updated dependencies [d374caf]
254
+ - Updated dependencies [5673576]
255
+ - Updated dependencies [c1ef923]
256
+ - Updated dependencies [911ceaa]
257
+ - Updated dependencies [98eab36]
258
+ - Updated dependencies [375efb4]
259
+ - Updated dependencies [af5e292]
260
+ - Updated dependencies [3fbbea1]
261
+ - Updated dependencies [0bffb18]
262
+ - Updated dependencies [3e0214c]
263
+ - Updated dependencies [800f455]
264
+ - Updated dependencies [dbbd38a]
265
+ - Updated dependencies [27c9cbd]
266
+ - Updated dependencies [5458414]
267
+ - Updated dependencies [3241559]
268
+ - Updated dependencies [7f96b10]
269
+ - Updated dependencies [167ec42]
270
+ - Updated dependencies [616a2a5]
271
+ - Updated dependencies [6c68b13]
272
+ - Updated dependencies [0046d8f]
273
+ - Updated dependencies [f1d4748]
274
+ - Updated dependencies [bea374e]
275
+ - Updated dependencies [b1119ec]
276
+ - Updated dependencies [5607092]
277
+ - Updated dependencies [9f23d2b]
278
+ - Updated dependencies [b4089be]
279
+ - Updated dependencies [578e025]
280
+ - Updated dependencies [b4bccc7]
281
+ - Updated dependencies [af025ee]
282
+ - Updated dependencies [d109a4d]
283
+ - Updated dependencies [598c89a]
284
+ - Updated dependencies [4a0bd17]
285
+ - Updated dependencies [b8b9af4]
286
+ - Updated dependencies [31676be]
287
+ - Updated dependencies [8c0d52e]
288
+ - Updated dependencies [b29488f]
289
+ - Updated dependencies [9fbb9b5]
290
+ - Updated dependencies [90517e1]
291
+ - Updated dependencies [aff10e2]
292
+ - Updated dependencies [70a774b]
293
+ - Updated dependencies [9ce096f]
294
+ - Updated dependencies [e05db88]
295
+ - Updated dependencies [7458a41]
296
+ - Updated dependencies [ad13d63]
297
+ - Updated dependencies [5ffcc14]
298
+ - Updated dependencies [d971e51]
299
+ - Updated dependencies [97abb24]
300
+ - Updated dependencies [deb157a]
301
+ - Updated dependencies [9c60144]
302
+ - Updated dependencies [e7747f1]
303
+ - Updated dependencies [d2ce342]
304
+ - Updated dependencies [9695da7]
305
+ - Updated dependencies [ac2f332]
306
+ - Updated dependencies [a777058]
307
+ - Updated dependencies [75444e3]
308
+ - Updated dependencies [58b8346]
309
+ - Updated dependencies [2d0bd16]
310
+ - Updated dependencies [a9e17b4]
311
+ - Updated dependencies [b8ce7dc]
312
+ - Updated dependencies [dad51e5]
313
+ - Updated dependencies [1c9c342]
314
+ - Updated dependencies [787c738]
315
+ - Updated dependencies [8396656]
316
+ - Updated dependencies [dbbd38a]
317
+ - Updated dependencies [2165d88]
318
+ - Updated dependencies [8871c14]
319
+ - Updated dependencies [93fe362]
320
+ - Updated dependencies [dfc6975]
321
+ - Updated dependencies [3cf4de0]
322
+ - Updated dependencies [c9dc811]
323
+ - Updated dependencies [144ef9b]
324
+ - Updated dependencies [138ab04]
325
+ - Updated dependencies [a0b9e91]
326
+ - Updated dependencies [99bd015]
327
+ - Updated dependencies [21e4585]
328
+ - @object-ui/types@17.6.0
329
+ - @object-ui/fields@17.6.0
330
+ - @object-ui/i18n@17.6.0
331
+ - @object-ui/react@17.6.0
332
+ - @object-ui/plugin-grid@17.6.0
333
+ - @object-ui/components@17.6.0
334
+ - @object-ui/core@17.6.0
335
+
336
+ ## 17.5.0
337
+
338
+ ### Patch Changes
339
+
340
+ - ee26e65: Analytics: the dimension label net's fetch-and-memo glue is written once, not once per surface
341
+
342
+ PR #4388 (objectui#4330) put the same React glue on two surfaces — the dashboard's `DatasetWidget` and plugin-report's dataset block. The resolution RULES were never duplicated (both call the same `@object-ui/core` helpers), but the wiring around them was: read the object schema through the host's authenticated `apiFetch`, keep the fetched metadata locale-free in state, derive the label maps in a render memo. Two copies meant two statements of the same two bug fixes, which is a drift surface rather than a defect — nothing a user could hit today, filed as objectui#4389 so it was retired deliberately.
343
+
344
+ It is now split along the layer that can actually hold each half. `@object-ui/core` gains the React-free parts — `loadDimensionFieldMeta` (the base-object read composed with the dimension walk), `deriveDimensionLabelMaps` (the locale-applying derivation) and `dimensionOptionTranslator` (binding the bundle resolver to the object that OWNS a terminal field, which for a dotted path is the relationship target). `@object-ui/react` gains `useDatasetDimensionLabels` / `useDatasetDimensionMeta`, the React wiring that cannot live in core, beside the `useViewData` / `useElementDataSource` / `useDiscovery` hooks that already read `SchemaRendererContext` the same way. Both plugins consume it; the dashboard keeps its chart-only per-category colour and category-order derivation layered locally, since a table renders no palette.
345
+
346
+ The card originally proposed `@object-ui/core` as the whole glue's home. That home was disproven by measurement and retired in the card's PM RULING #2: `SchemaRendererContext` is defined in `@object-ui/react`, which depends on core, so core importing it back is a cycle — and core is React-free by declaration, by content, and by the topology in AGENTS.md. objectui#3367 had already ruled this direction for the same family (core-canonical logic, react re-exports).
347
+
348
+ Behaviour is unchanged by construction: same read count, same best-effort fallback, same memoization boundary. The two bug fixes are now stated once and pinned at the shared hook — the read rides the host's authenticated `apiFetch` (objectui#4121, pinned by asserting that a new channel re-issues the read, i.e. that it really is in the effect's deps), and the fetched metadata stays locale-free (objectui#4030 / PR #4324, pinned by switching language at runtime and asserting the labels flip with no second metadata read). All 39 assertions PR #4388 landed across both surfaces pass unchanged, and their files are byte-identical to before.
349
+
350
+ - 326a70f: Analytics: a LOCAL select dimension on a table / pivot widget — and on a dataset-bound report — now renders its option label through the locale bundle
351
+
352
+ A dashboard table grouped by a select field showed `Domestic` on a zh-CN console while the related list on the same screen showed 国内. The value was never untranslated by accident: the server resolves that dimension's display label (ADR-0021) and hands the row over carrying the object's AUTHORED English label. The locale bundle is keyed by the option's stored VALUE (`{ns}.fieldOptions.<object>.<field>.<value>`), so translating one needs the option LIST — and the table path deliberately loaded no object metadata at all, which is why objectui#4030 / PR #4324 fixed charts and dotted dimensions and left this half open.
353
+
354
+ Table, pivot and the dataset report block now take the one metadata read that gives the bundle something to translate against, and feed it to the SAME seam #4324 landed (`resolveDimensionFieldMeta` → `localizeFieldOptions` / `buildDimensionLabelMap` → `relabelDimensions`). No second resolution dialect: the map carries both the stored value and the authored label as keys, and the relabel is value-wise and idempotent, so a value the server already resolved lands on the same display it would have from the raw value. Cells, pivot headers on both axes, the server's marginal totals, the CSV export and a report's embedded chart all read the one map, which is what keeps a subtotal's bucket lookup meeting the header it belongs to.
355
+
356
+ Untranslated apps are unchanged by construction: with no bundle entry the display equals the authored label, no key is emitted, and the rows come back by identity. Identity keys stay untranslated — a drilled row or cell still filters records by the values the server sent, and measures still export as bare numbers.
357
+
358
+ This deliberately amends the acceptance boundary objectui#4263 landed ("a local-only table issues no metadata read"), which was ruled for label RESOLUTION before the read had a second consumer. The pins that stated it are rewritten in place, in the same change, and say so.
359
+
360
+ - 49ae9f4: Pivot buckets encode an empty dimension value as JSON `null`, so it no longer collides with a row whose value is literally the placeholder character
361
+
362
+ objectstack#5473 / objectstack#5665 replaced the pivot's delimiter-joined ids
363
+ with `JSON.stringify`, because every delimiter that had been tried — an empty
364
+ string, a plain space, a control character — assumed the data would not contain
365
+ it, and each assumption failed on ordinary data. This closes the last place the
366
+ same assumption survived: the ids were JSON, but the VALUES fed into them were
367
+ spelled `String(row[d] ?? '∅')`, so an absent dimension value became the
368
+ ordinary string `"∅"` and shared a bucket with a row whose value literally is
369
+ that character (U+2205). One bucket, later row overwriting the earlier one — the
370
+ cell showed a different row's measure, the overwritten row was unreachable, and
371
+ drill-through followed the same wrong index into the wrong records, all without
372
+ an error. The trigger requires that character to appear as a dimension value, so
373
+ this is the assumption being removed rather than a defect users hit today.
374
+
375
+ An empty value now encodes as JSON `null`, which `JSON.stringify` renders as a
376
+ bare `null` that no string can spell. The normalization lives in
377
+ `@object-ui/core` as `pivotDimensionValue` (absent ⇒ `null`, everything else ⇒
378
+ its string form) rather than at each call site, because a placeholder spelled by
379
+ a caller is a placeholder that can collide again — which is exactly how this one
380
+ survived the previous fix. `pivotBucketId` accepts `Array<string | null>`
381
+ accordingly; that is a widening, so existing callers passing `string[]` are
382
+ unaffected.
383
+
384
+ Both renderers' bucket keys move together, which the fix requires: a bucket id
385
+ and the subtotal map keyed by it are built from the same expression, so changing
386
+ one alone would split the headers while the subtotal map still merged, landing
387
+ every column subtotal under the wrong header. In `plugin-dashboard`'s
388
+ `DatasetWidget` that is the row bucket id, the column bucket id, the cell key,
389
+ and both the `rowTotalById` and `colTotalById` lookups; in `plugin-report`'s
390
+ `DatasetReportRenderer` the single `bucketId` helper already feeds all five.
391
+
392
+ The dashboard's column bucket id also stops being a bare string and becomes a
393
+ one-element tuple through the same shared encoder. It was the one id in the
394
+ family still built by hand, on the reasoning that a single value needs no
395
+ boundary — true of the boundary, false of everything else the encoder does, and
396
+ it is why the across axis kept carrying this collision after the row ids were
397
+ fixed.
398
+
399
+ No display change: these placeholders only ever entered ids, never labels. An
400
+ unset dimension still renders through `formatDimensionValue` exactly as before,
401
+ and data containing neither an absent value nor that character buckets
402
+ identically — the ids are opaque lookup keys, never parsed back into a value,
403
+ never shown, never persisted.
404
+
405
+ - cb315f2: Report and dataset-preview measures follow the display locale (objectui#4575)
406
+
407
+ objectui#4566 gave `formatMeasure` / `formatDimensionValue` in `@object-ui/core`
408
+ an optional trailing `locale` and threaded `useDisplayLocale()` through the
409
+ dashboard's `DatasetWidget`. The parameter is OPTIONAL by design, so the
410
+ producer could land without dragging every consumer with it — which left the
411
+ consumers it did not reach still formatting in the MACHINE's locale. A German
412
+ session read a report measure as `1,234.5` directly beside a dashboard measure
413
+ that, after #4566, rendered `1.234,5`: one number, two spellings, on the same
414
+ screen. That is a sharper inconsistency than the one before #4566, when both
415
+ surfaces were uniformly wrong.
416
+
417
+ The remaining thirteen call sites now thread `useDisplayLocale()`:
418
+
419
+ - `plugin-report`'s `DatasetReportRenderer` (ten) — the grouped table's measure,
420
+ dimension and grand-total cells, the embedded single-value chart's metric, and
421
+ the cross-tab's across-axis header, down-axis cell, measure cell, row total,
422
+ column total and grand total;
423
+ - `app-shell`'s metadata-admin `DatasetPreview` (two) — the preview table's
424
+ measure and dimension cells;
425
+ - `app-shell`'s `DatasetDefaultInspector` (one) — the measure format-hint
426
+ sample, which is a preview of authored formatting and so has to be rendered
427
+ through the channel it previews.
428
+
429
+ **English output does not move**, and that is the discriminator against the
430
+ sibling fix. These sites already went through `Intl` with default grouping, so
431
+ the only thing that changes is WHOSE locale is used — contrast objectui#4553,
432
+ where `formatPercent` had never grouped at all and moving en `1235%` to
433
+ `1,235%` WAS the fix. Every new case pins the same value in de AND in en, so
434
+ at least one half must fail on any runner: before the change both render in the
435
+ machine's locale, which is what makes the machine locale stop being a test
436
+ input.
437
+
438
+ Two details worth recording:
439
+
440
+ - **The cross-tab's header labels are built inside a `useMemo`**, so the locale
441
+ joins that dependency array. Threading it into the call alone would leave the
442
+ headers frozen in whatever locale they were first built with — measured, and
443
+ pinned by a case that changes only the locale and asserts the header
444
+ re-labels. Removing just the dependency entry turns exactly that one case red
445
+ and leaves the other nine green.
446
+ - **The metadata designer's `locale` prop is deliberately not used.** It carries
447
+ the designer's own chrome language (`useMetadataLocale()`, which resolves to
448
+ exactly `en-US` or `zh-CN`), not a number-formatting locale — a German session
449
+ gets `en-US` from it. The preview's numbers have to match what the report and
450
+ dashboard render for the same dataset, which is `useDisplayLocale()`.
451
+
452
+ Both packages are `patch`: their published declarations are unchanged (measured
453
+ against the built `.d.ts` with `dist/` cleared between builds). The threading is
454
+ module-local, and the one signature that gained a parameter — the file-local
455
+ `bucketLabel` helper — is not exported.
456
+
457
+ A side effect of the fallback: these surfaces are now DETERMINISTIC where they
458
+ previously followed whatever locale the machine happened to run in.
459
+ `useDisplayLocale` ends at a concrete `'en'` rather than the `undefined` that
460
+ hands `Intl` the machine's locale.
461
+
462
+ - 3f5f87c: `SchemaRenderer` states its real contract — a typed, required `schema` and a deliberate forwarding surface
463
+
464
+ `SchemaRenderer` is the renderer loop: every registered SDUI component is rendered through it. It handed `forwardRef` a props type of `{ schema: SchemaNode } & Record<string, any>`, which puts `string` into `keyof Props`, so `'ref' extends keyof Props` was always true, React's `PropsWithoutRef` took its `Omit` branch, and `Omit` over a type carrying a string index signature keeps only the index signature. Every declared prop was erased. Measured on the pre-fix source: `keyof ComponentProps<typeof SchemaRenderer>` was `string` and `ComponentProps<typeof SchemaRenderer>['schema']` was `any`, while the type argument went on declaring `SchemaNode`. The other half is the same defect seen from the call site — `<SchemaRenderer />` with no schema at all, `<SchemaRenderer schema={12345} />`, and an arbitrary misspelled prop each type-checked in silence. This is objectui#4422 / PR #4438's trap in the most central component in the repo, spelled `Record<string, any>` rather than `[key: string]: any`, which is why every previous sweep's grep and both shipped guards' detector reported the site as clean.
465
+
466
+ Graded **minor, not major**, on objectui#4528's reasoning: the type argument has always DECLARED `schema`; the index signature erased it from the resolved type, and restoring what the declaration documents is a fix to the published contract rather than a contract break.
467
+
468
+ **The forwarding surface is kept, deliberately.** This component forwards every prop it does not read to the component the schema names, resolved at runtime from a plugin-extensible registry — `packages/react/README.md` documents exactly that, and `@object-ui/components`' form renderer consumes the `onSubmit` it shows being forwarded. Closing that surface would state a false contract and would force every leaf plugin's props into this package. So the two halves are separated: the `forwardRef` type argument is the honest `SchemaRendererProps`, with no index signature for `PropsWithoutRef` to collapse, and the open surface is stated once in an explicit export annotation, which nothing routes through `Omit`. The published `.d.ts` shows the erasure disappearing: `ForwardRefExoticComponent<Omit<{ schema: SchemaNode } & Record<string, any>, "ref"> & RefAttributes<any>>` becomes `ForwardRefExoticComponent<SchemaRendererProps & Record<string, any> & RefAttributes<any>>`.
469
+
470
+ `SchemaRendererProps.schema` is declared as `BaseSchema | string | null | undefined` — what this component actually handles. It previously declared `@object-ui/core`'s `SchemaNode` interface, which requires `type: string` and so contradicted the component's own early returns for strings and nullish, while every caller held `@object-ui/types`' wider union. The erasure hid that mismatch completely.
471
+
472
+ **One declared behaviour change.** A non-object, non-string primitive schema now renders as its own text. It previously fell through to the shallow copy `{ ...schema }`, which spreads a primitive to an empty object, lost the `type` the renderer then looked up, and surfaced the red "Unknown component type: undefined" box — an accident of the spread rather than a decision. The declared props type excludes `number` / `boolean` so no author is invited to pass them; the runtime handling is defence-in-depth for untyped callers and stored metadata. Strings, `null`, `undefined`, `0` and `false` render exactly as before, and an object naming an unregistered type still gets the error box; all four are pinned.
473
+
474
+ Latent defects the erasure had been hiding, each surfaced by the repo-wide type-check and fixed at its call site: `DashboardRenderer` cast its widget schema to `Record<string, any>`, dropping the `type` every branch of `getComponentSchema` sets; `DashboardGridLayout`'s equivalent now states its return type instead of inferring a union that admitted a shape with no `type`; and `ReportViewer` handed a section's `content` array to the renderer whole, so a multi-node section rendered the unknown-component box instead of its content — arrays are mapped rather than widened into the renderer's declared input.
475
+
476
+ A repo-wide structural guard replaces the two per-package siblings' blocked direction: it judges every `forwardRef` in `packages/*/src` (219 sites) and its detector resolves `Record<string, …>` and `string`-keyed mapped types in addition to literal index signatures — the spelling the previous detector went blind on. It judges the type argument only, where an index signature is an accidental eraser, and never an export annotation, where one is a stated contract.
477
+
478
+ - 31ab1ac: fix(print): `window.print()` produces a usable page, and the Print buttons say what they do
479
+
480
+ The list, report and dashboard Print controls were bare `window.print()` calls with no
481
+ print stylesheet, so the browser printed the whole console — sidebar, top bar, chat rail,
482
+ toasts — with the data table clipped to a single viewport. With no label to the contrary
483
+ they were being accepted against "export to PDF" requirements, which they have never been.
484
+
485
+ - `@object-ui/app-shell/styles.css` gains a shared `@media print` block: it hides the shell
486
+ chrome, prints the active content area full-width, releases the viewport-height flex chain
487
+ so long tables paginate instead of clipping, repeats table headers on every sheet, and
488
+ neutralises dark mode (which otherwise prints white-on-white). One sheet serves list,
489
+ report and dashboard.
490
+ - The list and report Print buttons carry a tooltip and accessible name stating that they
491
+ open the browser's own print dialog and are not a PDF export (new `common.printDialogHint`,
492
+ translated in all ten locale packs).
493
+ - The dashboard's `export_dashboard_pdf` action no longer toasts "Preparing PDF export…" —
494
+ it names the print dialog it actually opens (`dashboardActions.pdfPreparing` is replaced by
495
+ `dashboardActions.printDialogOpening`).
496
+
497
+ No control was removed and no headless detection was added. A real print/PDF primitive
498
+ remains out of scope (`objectstack-ai/objectstack#1301`, closed NOT_PLANNED).
499
+
500
+ - Updated dependencies [0e67b53]
501
+ - Updated dependencies [ceccdcf]
502
+ - Updated dependencies [d6e5124]
503
+ - Updated dependencies [debad27]
504
+ - Updated dependencies [dc2aa3e]
505
+ - Updated dependencies [ee66e2e]
506
+ - Updated dependencies [e2e6360]
507
+ - Updated dependencies [ee26e65]
508
+ - Updated dependencies [5900ac5]
509
+ - Updated dependencies [932cbcd]
510
+ - Updated dependencies [734d186]
511
+ - Updated dependencies [f650253]
512
+ - Updated dependencies [3d9769a]
513
+ - Updated dependencies [8f85f8b]
514
+ - Updated dependencies [7ffd616]
515
+ - Updated dependencies [d0c3b26]
516
+ - Updated dependencies [3fc2971]
517
+ - Updated dependencies [aca27fa]
518
+ - Updated dependencies [dde7283]
519
+ - Updated dependencies [f7c6430]
520
+ - Updated dependencies [4dadf0d]
521
+ - Updated dependencies [ae10a01]
522
+ - Updated dependencies [77d6f28]
523
+ - Updated dependencies [0f21348]
524
+ - Updated dependencies [d2e2caf]
525
+ - Updated dependencies [92876f0]
526
+ - Updated dependencies [f279deb]
527
+ - Updated dependencies [4b70d28]
528
+ - Updated dependencies [eb7f586]
529
+ - Updated dependencies [e901131]
530
+ - Updated dependencies [ebb4e0e]
531
+ - Updated dependencies [3a9021e]
532
+ - Updated dependencies [d9d3463]
533
+ - Updated dependencies [2a40f69]
534
+ - Updated dependencies [bec3e14]
535
+ - Updated dependencies [613b167]
536
+ - Updated dependencies [b4d3c22]
537
+ - Updated dependencies [1f9b905]
538
+ - Updated dependencies [8f60d73]
539
+ - Updated dependencies [cb13400]
540
+ - Updated dependencies [828549a]
541
+ - Updated dependencies [e1ade8f]
542
+ - Updated dependencies [bc64bfe]
543
+ - Updated dependencies [abb0f81]
544
+ - Updated dependencies [38ab505]
545
+ - Updated dependencies [51ab34e]
546
+ - Updated dependencies [24bb2de]
547
+ - Updated dependencies [0ca6096]
548
+ - Updated dependencies [3e19fe7]
549
+ - Updated dependencies [bb58d1d]
550
+ - Updated dependencies [433ff9f]
551
+ - Updated dependencies [5cc847c]
552
+ - Updated dependencies [e7663f2]
553
+ - Updated dependencies [fa21254]
554
+ - Updated dependencies [f565418]
555
+ - Updated dependencies [33c32bf]
556
+ - Updated dependencies [66fb4fa]
557
+ - Updated dependencies [b953a97]
558
+ - Updated dependencies [d7f3e30]
559
+ - Updated dependencies [6d641c9]
560
+ - Updated dependencies [7e4f0e5]
561
+ - Updated dependencies [a84385b]
562
+ - Updated dependencies [45e1949]
563
+ - Updated dependencies [51ac39f]
564
+ - Updated dependencies [5e514c4]
565
+ - Updated dependencies [92250d6]
566
+ - Updated dependencies [c1d939f]
567
+ - Updated dependencies [58bebf6]
568
+ - Updated dependencies [36310dc]
569
+ - Updated dependencies [52d878a]
570
+ - Updated dependencies [405e808]
571
+ - Updated dependencies [49ae9f4]
572
+ - Updated dependencies [a3ae404]
573
+ - Updated dependencies [4270c11]
574
+ - Updated dependencies [bfdf3d4]
575
+ - Updated dependencies [bb68488]
576
+ - Updated dependencies [c0f9a4b]
577
+ - Updated dependencies [b1e42d0]
578
+ - Updated dependencies [2459a3e]
579
+ - Updated dependencies [ac853ce]
580
+ - Updated dependencies [fa51109]
581
+ - Updated dependencies [d6aa172]
582
+ - Updated dependencies [fe52a04]
583
+ - Updated dependencies [d46f9b8]
584
+ - Updated dependencies [3f5f87c]
585
+ - Updated dependencies [2fea4d2]
586
+ - Updated dependencies [f5e1143]
587
+ - Updated dependencies [7f1cb33]
588
+ - Updated dependencies [f148a64]
589
+ - Updated dependencies [bb68488]
590
+ - Updated dependencies [2e3b0c0]
591
+ - Updated dependencies [9461dd3]
592
+ - Updated dependencies [78fa331]
593
+ - Updated dependencies [47f551b]
594
+ - Updated dependencies [31ab1ac]
595
+ - Updated dependencies [0082db8]
596
+ - Updated dependencies [ab04728]
597
+ - Updated dependencies [5bf09fd]
598
+ - Updated dependencies [06915b0]
599
+ - Updated dependencies [ff84b05]
600
+ - @object-ui/i18n@17.5.0
601
+ - @object-ui/react@17.5.0
602
+ - @object-ui/components@17.5.0
603
+ - @object-ui/core@17.5.0
604
+ - @object-ui/fields@17.5.0
605
+ - @object-ui/types@17.5.0
606
+ - @object-ui/plugin-grid@17.5.0
607
+
608
+ ## 17.4.0
609
+
610
+ ### Patch Changes
611
+
612
+ - 3b1f888: Accept React 19 in `@object-ui/plugin-report`'s peer range, the last UI package still declaring React 18 alone (objectui#3690).
613
+
614
+ `peerDependencies.react` and `peerDependencies.react-dom` widen from `^18.0.0` to `^18.0.0 || ^19.0.0`, matching the other 29 packages in the fixed version group. With npm 7+ resolving peers strictly, a React 19 consumer installing this published package hit an `ERESOLVE` on first install while every sibling installed clean — and the package's own README already documented the wider range, so the manifest was the half that was wrong.
615
+
616
+ The narrow range was never a constraint anyone stated. `packages/plugin-report/package.json` was hand-authored on 2026-02-06 (`1e557cbda`), by which point nineteen sibling packages already carried `^18.0.0 || ^19.0.0` and every package created afterwards was born with it; the one other package born narrow, `plugin-dashboard`, was corrected on 2026-05-08 (`d2b6ecec6`) in a build fix that touched only itself. No commit in the file's 172-commit history ever revisited the peer line, and no commit message mentions a React 18 requirement.
617
+
618
+ Nothing in the package needs React 18. Its entire React surface is `React.FC`, `useState`, `useEffect`, `useMemo`, `useReducer`, `useContext`, `Fragment`, `ComponentType`, `CSSProperties` and `ReactNode` — all unchanged in React 19 — with zero uses of anything React 19 removed (`ReactDOM.render`, `unmountComponentAtNode`, `findDOMNode`, legacy context, string refs, `defaultProps` / `propTypes` on function components, `createFactory`, `useFormState`, `react-dom/test-utils`). `react-dom` is not imported by the source at all; it appears only as a UMD global name in the Vite externals config. The workspace pins `react` to 19.2.8 via a root `pnpm.overrides`, so this package's 78 tests have been running against React 19 the whole time it declared it did not support it.
619
+
620
+ - Updated dependencies [794c497]
621
+ - Updated dependencies [993336f]
622
+ - Updated dependencies [f0a625a]
623
+ - Updated dependencies [b5980f4]
624
+ - Updated dependencies [8aad9fd]
625
+ - Updated dependencies [6719877]
626
+ - Updated dependencies [56ff091]
627
+ - Updated dependencies [0186cdc]
628
+ - Updated dependencies [7864f03]
629
+ - Updated dependencies [ea41a59]
630
+ - Updated dependencies [0cbdca8]
631
+ - Updated dependencies [d229dfa]
632
+ - Updated dependencies [18c42c6]
633
+ - Updated dependencies [ecae400]
634
+ - Updated dependencies [4bc6c23]
635
+ - Updated dependencies [d3e738a]
636
+ - Updated dependencies [c3b01a7]
637
+ - Updated dependencies [f5f8744]
638
+ - Updated dependencies [7ed3360]
639
+ - Updated dependencies [69becd2]
640
+ - Updated dependencies [5e52495]
641
+ - Updated dependencies [0fa5e4d]
642
+ - Updated dependencies [b750823]
643
+ - Updated dependencies [5bfaabd]
644
+ - Updated dependencies [e06810e]
645
+ - Updated dependencies [ab3ad4f]
646
+ - Updated dependencies [65bb513]
647
+ - Updated dependencies [c97a45e]
648
+ - Updated dependencies [b19162d]
649
+ - Updated dependencies [c2fd122]
650
+ - Updated dependencies [1bd6faa]
651
+ - Updated dependencies [9154d9e]
652
+ - Updated dependencies [ac2139c]
653
+ - Updated dependencies [b14ab3a]
654
+ - Updated dependencies [e24d767]
655
+ - Updated dependencies [8c60819]
656
+ - Updated dependencies [aca561a]
657
+ - Updated dependencies [e64a52e]
658
+ - Updated dependencies [844d17f]
659
+ - Updated dependencies [d8a0be4]
660
+ - Updated dependencies [48132f7]
661
+ - Updated dependencies [4dcd52a]
662
+ - Updated dependencies [42ae5c6]
663
+ - Updated dependencies [0ef9dfd]
664
+ - Updated dependencies [f4b97c8]
665
+ - Updated dependencies [1d723e3]
666
+ - Updated dependencies [0109f54]
667
+ - Updated dependencies [7e5bb5d]
668
+ - Updated dependencies [fbc23e0]
669
+ - Updated dependencies [6d762da]
670
+ - Updated dependencies [e6fdbdc]
671
+ - Updated dependencies [54233b1]
672
+ - Updated dependencies [c2ecbae]
673
+ - Updated dependencies [f9faa7d]
674
+ - Updated dependencies [97b63d7]
675
+ - Updated dependencies [14c59c0]
676
+ - Updated dependencies [aeb8424]
677
+ - Updated dependencies [6bb454a]
678
+ - Updated dependencies [1a33b1a]
679
+ - Updated dependencies [11c1e71]
680
+ - Updated dependencies [523be48]
681
+ - Updated dependencies [7e2b7e9]
682
+ - Updated dependencies [33526fd]
683
+ - Updated dependencies [32413ec]
684
+ - Updated dependencies [c1e1e6b]
685
+ - @object-ui/components@17.4.0
686
+ - @object-ui/react@17.4.0
687
+ - @object-ui/core@17.4.0
688
+ - @object-ui/fields@17.4.0
689
+ - @object-ui/i18n@17.4.0
690
+ - @object-ui/types@17.4.0
691
+ - @object-ui/plugin-grid@17.4.0
692
+
3
693
  ## 17.3.0
4
694
 
5
695
  ### Patch Changes
@@ -1425,6 +2115,7 @@
1425
2115
  The matrix/summary "Group by" (rows) and "Columns axis" (cols) sections now
1426
2116
  share the same searchable popup picker as the columns section, with a
1427
2117
  commit-on-select single-pick mode wired through `FieldPickerDialog`.
2118
+
1428
2119
  - Per-row field buttons display the human-readable field label and open a
1429
2120
  dialog scoped to swap that single field (already-used fields filtered out)
1430
2121
  - "Add grouping" trigger uses the same dialog
@@ -1434,6 +2125,7 @@
1434
2125
  - Bigger row spacing (h-7 / text-xs) — the old `text-[10px]` was unreadable
1435
2126
 
1436
2127
  `FieldPickerDialog` gains:
2128
+
1437
2129
  - `commitOnSelect`: hides the Confirm/Cancel footer; clicking a row commits
1438
2130
  - closes immediately (intended for `singleSelect` flows)
1439
2131
  - `trigger`: custom trigger element override (used by the per-row field button)
@@ -1483,6 +2175,7 @@
1483
2175
  in the spec.
1484
2176
 
1485
2177
  New exports from `@object-ui/plugin-report`:
2178
+
1486
2179
  - `JoinedBlocksEditor` — standalone component for embedding the
1487
2180
  block editor anywhere.
1488
2181
  - `validateJoinedBlocks` — pure helper returning translated
@@ -1513,6 +2206,7 @@
1513
2206
  The report configuration panel is now safe to open on any spec-shape `Report` and only exposes fields that are actually persisted by `@objectstack/spec`.
1514
2207
 
1515
2208
  `@object-ui/plugin-report`:
2209
+
1516
2210
  - Add a bidirectional `SpecFilterAdapter` so `ReportConfigPanel` can edit
1517
2211
  spec `FilterCondition` filters (`{field: value}`, `{field: {$op: value}}`,
1518
2212
  top-level `$and`/`$or`). Complex / nested filters fall back to a
@@ -1534,12 +2228,14 @@
1534
2228
  - 18 new unit tests cover the filter adapter round-trip.
1535
2229
 
1536
2230
  `@object-ui/components`:
2231
+
1537
2232
  - `FilterBuilder` now guards against malformed external `value` props.
1538
2233
  Previously a spec-shape filter (`{is_active: true}`) would crash the
1539
2234
  component on first render; the builder now falls back to an empty
1540
2235
  AND group whenever `value` is not a valid `FilterGroup`.
1541
2236
 
1542
2237
  `@object-ui/i18n`:
2238
+
1543
2239
  - Add `report.editor.*` strings to `en` and `zh`.
1544
2240
 
1545
2241
  - 8442c05: Improve report editor panel usability based on real-user browser testing:
@@ -1600,6 +2296,7 @@
1600
2296
 
1601
2297
  - 650392e: MatrixRenderer now displays i18n-translated labels for picklist (`select` / `status`) groupings instead of raw values (e.g. `Best Case` / `Commit` / `Pipeline` instead of `best_case` / `commit` / `pipeline`). Field labels in the corner cell, row/column total labels, and the `(Empty)` / `(All)` placeholders are also fully translated. Adds `report.*` keys to `en` and `zh` locale bundles.
1602
2298
  - 26f5fce: Simplify report identity: replace the dashboard-style KPI grid with a compact "Totals" strip so reports look like reports (table-first with a grand total), not like mini-dashboards.
2299
+
1603
2300
  - `SpecReportGrid` now renders one inline `Totals: Label1: value Label2: value …` strip above the chart and table, styled as a muted single-line band — clearly subordinate to the data grid below.
1604
2301
  - The Totals strip is now also shown for `tabular` reports when they declare aggregating columns (matches Salesforce's "Grand Total" convention).
1605
2302
  - Drop the duplicate chart title `<div>`: the chart component already renders its own title from `report.chart.title`.
@@ -1627,6 +2324,7 @@
1627
2324
  ### Minor Changes
1628
2325
 
1629
2326
  - b4ce9e2: Fix summary reports: render chart + KPIs, correct empty-table on server-aggregated data.
2327
+
1630
2328
  - `plugin-report`: `SpecReportGrid` now renders a KPI strip (per aggregating column) and a chart section above the grid for `summary` reports. KPI section auto-hides when no aggregating columns. New `buildChartData()` adapter buckets aggregated `ReportRow[]` to chart-ready data, auto-sorts pie/funnel descending, and falls back to row count when the chart `yAxis` points at a non-numeric column. When the data is server-aggregated, the grid switches columns to `[groupings, ${field}__${agg}]` so cells aren't empty against a raw-row column schema.
1631
2329
  - `plugin-charts`: register `'column'` as an alias of `'bar'` in `ChartRenderer` / `AdvancedChartImpl` (Recharts only has `BarChart`).
1632
2330
  - `app-shell`: `ReportView` now routes any object-backed report (matrix/joined/summary/tabular/columns/groupingsAcross) through the spec `ReportRenderer`; fully-legacy `fields`+`data` schemas still use `ReportViewer`.
@@ -1770,6 +2468,7 @@
1770
2468
  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.
1771
2469
 
1772
2470
  Specifically fixes:
2471
+
1773
2472
  - `@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.
1774
2473
  - `@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.
1775
2474
  - All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.