@object-ui/plugin-dashboard 17.3.0 → 17.5.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 +922 -0
- package/README.md +52 -15
- package/dist/DashboardFilterBar.d.ts.map +1 -1
- package/dist/DashboardGridLayout.d.ts +27 -0
- package/dist/DashboardGridLayout.d.ts.map +1 -1
- package/dist/DashboardRenderer.d.ts +46 -4
- package/dist/DashboardRenderer.d.ts.map +1 -1
- package/dist/DashboardWithConfig.d.ts.map +1 -1
- package/dist/DatasetWidget.d.ts +174 -6
- package/dist/DatasetWidget.d.ts.map +1 -1
- package/dist/MetricCard.d.ts +29 -10
- package/dist/MetricCard.d.ts.map +1 -1
- package/dist/MetricWidget.d.ts +82 -17
- package/dist/MetricWidget.d.ts.map +1 -1
- package/dist/ObjectDataTable.d.ts.map +1 -1
- package/dist/ObjectMetricWidget.d.ts +10 -7
- package/dist/ObjectMetricWidget.d.ts.map +1 -1
- package/dist/RecordDetailDrawer.d.ts.map +1 -1
- package/dist/WidgetConfigPanel.d.ts.map +1 -1
- package/dist/colorVariants.d.ts +56 -0
- package/dist/colorVariants.d.ts.map +1 -0
- package/dist/domPassthroughPins.d.ts +9 -0
- package/dist/domPassthroughPins.d.ts.map +1 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1506 -1268
- package/dist/index.umd.cjs +2 -2
- package/dist/legacyRetiredWidget.d.ts +41 -0
- package/dist/legacyRetiredWidget.d.ts.map +1 -0
- package/dist/recordFields.d.ts +1 -1
- package/dist/recordFields.d.ts.map +1 -1
- package/dist/schemaHostProps.d.ts +80 -0
- package/dist/schemaHostProps.d.ts.map +1 -0
- package/dist/utils.d.ts +34 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +13 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,921 @@
|
|
|
1
1
|
# @object-ui/plugin-dashboard
|
|
2
2
|
|
|
3
|
+
## 17.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7084f7d: `DashboardRenderer` and `ListView` serve the props they declare — the index signature stops erasing them
|
|
8
|
+
|
|
9
|
+
Both components declared a full props interface and neither was enforced. A `[key: string]: any` on `DashboardRendererProps` and `ListViewProps` puts `string` into `keyof Props`, so `'ref' extends keyof Props` is always true and React's `PropsWithoutRef` takes its `Omit` branch — and `Omit` over a type carrying a string index signature keeps only the index signature. Every declared property was dropped from the resolved type, on both sides: the render function received `{ [x: string]: any }` (so even `schema` was `any` inside the component), and every JSX call site was unchecked. Measured on the pre-fix source, `keyof ComponentProps<typeof DashboardRenderer>` was `string | number` and `ComponentProps<typeof DashboardRenderer>['onWidgetClick']` was `any`, while the interface went on declaring `(widgetId: string | null) => void`. `ListView` measured identically for `onRowClick`. This is objectui#4422 / PR #4438's trap in the two packages that issue left unswept.
|
|
10
|
+
|
|
11
|
+
Graded **minor, not major**: the interfaces have always DECLARED these props; the index signature erased them from the resolved type. Restoring what the interface documents is a FIX to the published contract, not a contract break — no documented capability is removed, and `any`-typed accidental passthrough was never the documented surface. Nothing in either package's README or docs endorses relying on it.
|
|
12
|
+
|
|
13
|
+
The props each component genuinely reads but never declared are now declared by name, at the type each one lands on: `dataSource` on both, plus `onAddRecord` / `onBulkAction` / `onPageSizeChange` / `onEdit` / `onDelete` / `onBulkDelete` on `ListView`. `DashboardRenderer`'s DOM pass-through keys are derived from `toDomProps`' whitelist constant itself, so the declaration and the runtime filter cannot drift — the "declare it and forward it by name" direction `@object-ui/core`'s `dom-props` doctrine asks for, rather than reopening the spread.
|
|
14
|
+
|
|
15
|
+
Type-only: the emitted JS for both packages is byte-identical before and after (verified by sha256 on `dist/index.js` and `dist/index.umd.cjs`), and both packages' runtime suites are untouched and green.
|
|
16
|
+
|
|
17
|
+
Three latent defects the erasure had been hiding are fixed with it, each surfaced by the repo-wide type-check: `DashboardWithConfig` typed its widget-select handler `(widgetId: string)` while `DashboardRenderer` calls `onWidgetClick(null)` to deselect; `InterfaceListPage` built a list schema whose `viewType` was a bare `string`; and `StudioDesignSurface` forwarded a `refreshKey` prop that no component in the chain declares or reads, so it was silently dropped. Per-package structural guards now pin the shape in both packages, covering the public `forwardRef` that takes its props whole — the spelling objectui#4438's `schema`-destructuring scan could not see.
|
|
18
|
+
|
|
19
|
+
- b8bda9a: The editable dashboard grid renders dataset-bound widgets — and says so visibly when it cannot
|
|
20
|
+
|
|
21
|
+
`DashboardGridLayout` had no dataset path at all. It never read `widget.dataset`, never imported `DatasetWidget`, and took no `dataSource` prop — so a widget authored the way ADR-0021 says to author them (`{ id, type: 'bar', dataset: 'invoices', values: ['count'] }`) fell straight through to the static-data branch and rendered nothing. Measured on the node the grid handed `SchemaRenderer`, the silence had three flavours rather than the one reported: a `bar` became `{ type: 'chart', data: [] }` (a chart drawn over nothing), a `metric` became `{ type: 'metric', value: '—' }` (an em dash, which reads as a rendered value rather than an error), and a `table` became `{ type: 'data-table', data: [] }`. No data, no diagnostic, no path to fix — on the surface registered as the `dashboard-grid` SDUI component and exported by name from the package entry.
|
|
22
|
+
|
|
23
|
+
This is the defect objectui#4612 fixed for the RETIRED authoring shape, one level up: same surface, same silence, but the shape that is current. The sibling `DashboardRenderer` has routed these widgets through the governed `queryDataset` path since ADR-0021, so the cure is that surface's own mechanics rather than a second dispatch idiom — the `datasetBound` predicate decided per widget, and `DatasetWidget` picked at the render site.
|
|
24
|
+
|
|
25
|
+
`DashboardGridLayout` therefore gains an optional `dataSource` prop, forwarded to `DatasetWidget` for dataset-bound widgets. A dataset-bound metric now also takes the shared `Card` wrapper, matching the sibling: `DatasetWidget` renders just the value, so without the card it would show as bare text with no title beside its neighbours.
|
|
26
|
+
|
|
27
|
+
A dataset-bound widget arriving with NO data source renders a visible state, never a blank. No new placeholder was declared for it: `DatasetWidget`'s own no-capability rendering — an alert reading "This data source does not support dataset queries." — was measured to render visibly when handed no adapter, so routing through it unconditionally cures both halves with one diagnostic and one wording. That case is not hypothetical: `dashboard-grid`'s SDUI registration declares only `title` and `className` inputs, so schema-driven hosts render this component with no adapter at all, and every such host keeps working exactly as before.
|
|
28
|
+
|
|
29
|
+
Nothing else moves. The objectui#4612 legacy sentinel keeps its position and its verdict — the two conditions are mutually exclusive by construction, since the shared detector returns false the moment a widget carries `dataset` — and static-data widgets, `options.data` provider widgets and legacy-retired widgets all render as they did and never reach the dataset query. The new prop is additive and optional, so existing call sites are untouched.
|
|
30
|
+
|
|
31
|
+
- 8640cec: One legacy detector, two dashboard surfaces — the editable grid stops rendering a silent blank chart
|
|
32
|
+
|
|
33
|
+
framework#3320 retired the pre-ADR-0021 inline-analytics widget shape (top-level `object` + `categoryField` / `valueField` / `aggregate`, pivot `rowField` / `columnField`) and shipped a graceful fallback for the stored metadata that still carries it: a visible tile reading "This widget uses a retired data format. Edit it to bind a dataset." The fallback was applied to `DashboardRenderer` and to nothing else. `DashboardGridLayout` — separately exported, and registered as the `dashboard-grid` SDUI component — had no sentinel at all, so the identical stored widget fell through to its static-data branch with `data: []`. Same metadata, same product, two different outcomes: a rebind prompt on one surface, a silent blank chart on the other. The blank is worse for the author than the pre-retirement state, because it carries no chart, no diagnostic and no path to fix — the exact outcome the retirement's own test header says must not happen.
|
|
34
|
+
|
|
35
|
+
The fix is not a second copy of the condition, because one copy is why the defect existed. The detector and the placeholder now live in a single module (`legacyRetiredWidget.ts`) that both surfaces import; `DashboardRenderer`'s observable behaviour is unchanged, pinned by its existing suite, and the new grid suite mirrors that suite's structure on the surface nobody had pinned. Four positive cases go from blank to placeholder, two of them the widget shapes stored byte-for-byte in the schema catalog's `filtered-dashboard` entry.
|
|
36
|
+
|
|
37
|
+
The negative controls are the load-bearing half, because the retired shape is one character away from a live one. `options.data = { provider: 'object', … }` carries its OWN nested `object` and `aggregate`: it is a different, still-live authoring surface, read off the widget's data rather than off the widget top level, and it keeps rendering untouched — as do dataset-bound widgets and static-data widgets, on both surfaces. `DashboardRenderer`'s pivot arm stays deliberately surface-local rather than shared: it returns the placeholder for the entire pivot family because that surface emits no pivot block at all, which is a fact about what it can draw, not about the widget being legacy. The grid does draw pivots, from static data and from the provider config, so exporting that arm would have retired two working branches. A legacy pivot is caught on both surfaces by the shared sentinel instead, via the top-level `object` it carries.
|
|
38
|
+
|
|
39
|
+
- f244273: `MetricWidgetProps` / `MetricCardProps` declare the DOM pass-through their spread has always accepted
|
|
40
|
+
|
|
41
|
+
Both KPI components end their prop list with a `...domProps` spread onto the Shadcn `Card`, and objectui#4357 (PR #4428) kept that spread deliberately — it is their only accessibility pass-through, and removing it would delete the only way a host can put an `id`, a `role` or an `aria-label` on a KPI card. Neither props interface declared any of it. So the type refused what the runtime accepted: a JS consumer, and every SDUI author going through `SchemaRenderer` (untyped at that boundary), got the pass-through, while a TypeScript consumer importing the component directly got `error TS2322` on `id` / `role` / `aria-label` and needed a cast.
|
|
42
|
+
|
|
43
|
+
`MetricWidgetProps` now extends `React.HTMLAttributes<HTMLDivElement>`, and `MetricCardProps` extends the same minus `title`. That is the repo's measured convention for an exported props interface that spreads onto a host element (`PageHeaderComponentProps`, `ChatbotProps`, `ChatbotEnhancedProps`, `TypingIndicatorProps`, `RefreshIndicatorProps`, `FieldProps`, and shadcn's `BadgeProps`), and the `Omit` carve-out is `ComboboxProps`'s spelling for a name the component's own contract owns.
|
|
44
|
+
|
|
45
|
+
Graded `minor` rather than `patch` per the objectui#4403 precedent: two exported interfaces widen. The widening is purely additive for existing callers — every prop that compiled before still compiles, and nothing narrows — so no source change is required to upgrade.
|
|
46
|
+
|
|
47
|
+
Semantics worth knowing, because both are contract statements rather than incidental:
|
|
48
|
+
|
|
49
|
+
- **`MetricCard.title` stays the heading.** HTML's `title` is a tooltip; this card's `title` is its heading, in the `I18nLabel` vocabulary, destructured out and rendered into `CardTitle`. No `title` attribute has ever reached this element, so the inherited DOM `title` is omitted rather than declared and silently dropped — the "declared but not delivered" failure this repo treats as first-class (objectui#3290, objectui#3222). `MetricWidget` has no such collision (its heading is `label`) and extends the DOM attributes whole.
|
|
50
|
+
- **`MetricWidget.onClick` stays zero-arg**, narrower than the inherited `MouseEventHandler`, because the same handler is wired to Enter/Space where there is no mouse event to hand over. A zero-arg function is assignable to the inherited signature, so callers already passing `(e) => …` keep compiling.
|
|
51
|
+
|
|
52
|
+
Not declared, deliberately: the schema-shaped keys `SchemaRenderer` injects (`schema` / `bind` / `events` / `props` / `ariaLabel` / `ariaDescribedBy` / `dataSource`). None is an HTML attribute name, all seven are destructured out before the spread, and declaring them would re-assert as public contract exactly what PR #4428 stripped from the DOM. They stay in `SchemaHostProps`, intersected in at each component's own signature — accepted so the renderer can inject them, never part of the documented authoring surface.
|
|
53
|
+
|
|
54
|
+
Zero runtime change: no component body was touched, and PR #4428's pins pass untouched.
|
|
55
|
+
|
|
56
|
+
- c1d939f: One `SchemaNode`, and one label vocabulary — the union wins, and labels resolve where the locale lives
|
|
57
|
+
|
|
58
|
+
Two packages published a type called `SchemaNode` and they were not the same type. `@object-ui/core` hand-declared `interface SchemaNode { type: string; … [key: string]: any }`; `@object-ui/types` exported `type SchemaNode = BaseSchema | string | number | boolean | null | undefined`, whose own doc comment names `'Plain string'` a valid node. Both were exported under one name from packages the same consumers import together, so which declaration a call site got depended on which package it happened to import from — #4548's canary measured 19 of 35 errors as exactly that collision. Core's declaration is now a re-export of types', so there is one declaration left to disagree with. Core's entry surface is unchanged: `dist/index.d.ts` is byte-identical across the change.
|
|
59
|
+
|
|
60
|
+
Reconciling it exposed a real defect rather than a mechanical narrowing, which is why the first attempt was withdrawn instead of forced. The spec bridges write `spec.label` — the spec's `I18nLabel`, an INLINE locale map like `{ en: 'Owner', 'zh-CN': '负责人' }` — into `node.label`, and `BaseSchema.label` declared `string`. Under core's old index signature that assignment was invisibly `any`; under one honest `SchemaNode` it is a type error. `BaseSchema.label` and `.description` therefore now accept `string | I18nLabel`, and the two bridge assignments compile with their expressions untouched.
|
|
61
|
+
|
|
62
|
+
Resolution happens at READ time, in the renderer, against the display locale — not at the bridge. Resolving at the bridge was measured unimplementable: it is a plain class method that cannot call a hook, `BridgeContext` declares no locale, and `updateContext()` has zero callers, so a bridge-resolved label would freeze one audience's language into the node tree with no re-translation channel. React's own invalidation re-translates for free at the read site.
|
|
63
|
+
|
|
64
|
+
The widening turned every blind `schema.label`-as-string read into a named compiler error, and that inventory is the audit: it named four sites repo-wide, all one class — the label reaching a React child position, where a map does not render as `[object Object]` but THROWS `Objects are not valid as a React child`, failing the whole subtree. Three are `@object-ui/components` renderers (`filter-builder`, `sidebar-group`, `dropdown-menu`), which now resolve with the spec's own `resolveI18nLabel` against `useDisplayLocale()`. The fourth is `plugin-dashboard`'s `DashboardGridLayout` heading, which resolves with `pickLocalized` against the active UI language — matching the widget-title resolution already in that same component rather than putting two resolvers and two disagreeing locale channels in one render; the two resolvers are limb-for-limb twins with a parity test pinning them.
|
|
65
|
+
|
|
66
|
+
One interface now carries both label vocabularies two properties apart — `label`/`description` are the spec's INLINE map, `ariaLabel` is the KEYED bundle reference — and each accepts the other's shape vacuously. That confusability is objectui#4167's known hazard, inherent to the spec's `I18nLabel` design; both shapes are named with cross-referenced doc comments stating which resolver owns which slot, and a pin asserts the two unions do not collapse into each other.
|
|
67
|
+
|
|
68
|
+
Finally, the spec bridges declare their return type as `BaseSchema` instead of the union. Both bridges end in a single `return node` on an object literal, so the union described nothing real while forcing a narrowing at every read — 272 mechanical errors across five suites in the first round. That change is a type annotation only; the emitted JavaScript is byte-identical.
|
|
69
|
+
|
|
70
|
+
- 36310dc: `formatPercent` groups its output and follows the display locale — the last
|
|
71
|
+
tooltip/cell channel (objectui#4553).
|
|
72
|
+
|
|
73
|
+
PR #4557 threaded the gantt tooltip's number and currency rows and measured that
|
|
74
|
+
the percent row could not follow: `formatPercent(value, precision)` took no
|
|
75
|
+
locale parameter, and its whole body was
|
|
76
|
+
`${percentDisplayValue(value).toFixed(precision)}%`. It built no
|
|
77
|
+
`Intl.NumberFormat` and never reached `formatDisplayNumber` — so unlike its
|
|
78
|
+
siblings it did not render in the MACHINE's locale, it rendered in **no** locale:
|
|
79
|
+
an ASCII decimal mark, never a grouping separator, byte-identical on every
|
|
80
|
+
machine.
|
|
81
|
+
|
|
82
|
+
**English output MOVES, and that is the fix.** Because the function never
|
|
83
|
+
grouped, `1235%` was wrong in en-US too, not only in German. Grouping and locale
|
|
84
|
+
therefore land together:
|
|
85
|
+
|
|
86
|
+
| | before | after |
|
|
87
|
+
| ---------- | ------- | -------------- |
|
|
88
|
+
| en, 1234.5 | `1235%` | `1,235%` |
|
|
89
|
+
| de, 1234.5 | `1235%` | `1.235\u00a0%` |
|
|
90
|
+
| de, 80 | `80%` | `80\u00a0%` |
|
|
91
|
+
|
|
92
|
+
Values below the grouping threshold are unchanged in English (`80%`, `12.5%`,
|
|
93
|
+
`33.33%`), so the move is confined to four digits and up. German changes at every
|
|
94
|
+
magnitude, because the no-break space before the sign is part of the locale's
|
|
95
|
+
percent convention — which is what routing through `Intl` buys over appending a
|
|
96
|
+
literal `%`.
|
|
97
|
+
|
|
98
|
+
The scaling contract is untouched: `percentDisplayValue` still disambiguates a
|
|
99
|
+
fraction-stored percent (`0.8` → 80%) from a whole one, so the list cell and the
|
|
100
|
+
dashboard measure formatter still agree.
|
|
101
|
+
|
|
102
|
+
Consumers are threaded in the same change, the parameter never landing
|
|
103
|
+
speculatively:
|
|
104
|
+
|
|
105
|
+
- **fields** — `PercentCellRenderer`, on BOTH of its paths. Its whole-percent
|
|
106
|
+
branch (`progress` / `completion` fields, which store 0-100 and must skip the
|
|
107
|
+
fraction scaling) was a second bare `toFixed` call; leaving it behind would
|
|
108
|
+
have made one grid internally inconsistent, so both branches now share one
|
|
109
|
+
locale-aware body and differ only in the scaling policy.
|
|
110
|
+
- **plugin-gantt** — the tooltip percent row, completing objectui#4553's switch.
|
|
111
|
+
- **plugin-grid** — the mobile card's percent cell, which sits in the same
|
|
112
|
+
density row as a date cell objectui#4272 had already localized.
|
|
113
|
+
- **plugin-dashboard** — `renderFieldValue`'s percent branch. It is a plain
|
|
114
|
+
function rather than a component, so it takes the locale as an optional fourth
|
|
115
|
+
parameter beside the `tenantCurrency` already threaded that way, and both of
|
|
116
|
+
its callers pass it and declare it in their memo dependency arrays.
|
|
117
|
+
|
|
118
|
+
Bumps follow each package's own `.d.ts` diff, measured in both directions.
|
|
119
|
+
`@object-ui/fields` and `@object-ui/plugin-dashboard` are `minor` on the
|
|
120
|
+
objectui#4272 / PR #4544 precedent — quoted from that changeset: "`@object-ui/fields`
|
|
121
|
+
is `minor` because `formatDateTime`'s new optional parameter is visible in the
|
|
122
|
+
package's entry `.d.ts`; the plugin packages' own `.d.ts` files are
|
|
123
|
+
byte-identical, so their change is module-local." Here `formatPercent` and
|
|
124
|
+
`renderFieldValue` each gain an entry-visible optional parameter, while
|
|
125
|
+
plugin-gantt's and plugin-grid's `.d.ts` files are byte-identical and stay
|
|
126
|
+
`patch`.
|
|
127
|
+
|
|
128
|
+
### Patch Changes
|
|
129
|
+
|
|
130
|
+
- ee26e65: Analytics: the dimension label net's fetch-and-memo glue is written once, not once per surface
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
134
|
+
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.
|
|
135
|
+
|
|
136
|
+
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).
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
- 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
|
|
141
|
+
|
|
142
|
+
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.)
|
|
143
|
+
|
|
144
|
+
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.
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
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.
|
|
151
|
+
|
|
152
|
+
- 3c6e84c: Dashboard `combo` widgets draw as combos on the dataset path — the dataset owns the data, the author owns the presentation
|
|
153
|
+
|
|
154
|
+
A widget authoring the spec's own combo shape — `series[].type` plus `series[].yAxis: 'left'|'right'` and two `yAxis` entries — rendered as two bar series on one shared axis. Measured in the DOM: 2 bars, 0 lines, 1 y-axis, where 1 bar, 1 line and 2 axes were authored, so a percentage measure was plotted against a raw count's scale.
|
|
155
|
+
|
|
156
|
+
Two halves caused it, and fixing either alone leaves a worse state than before. `CHART_TYPE_MAP` had no `combo` entry, so a `combo` widget fell through its `?? 'bar'` default — bars, whatever the series said. And `chartConfigPresentation` refused to forward `series` / `xAxis` / `yAxis` at all, on the stated grounds that they are derived from the dataset selection, so the per-series mark and the axis binding could never reach the renderer even once the family resolved.
|
|
157
|
+
|
|
158
|
+
That belief was half right. The dataset does own the series MEMBERSHIP — which columns become series, which rows, which buckets — and it still does: an authored entry naming a measure the dataset did not select is ignored, and a derived series the author said nothing about keeps the family default. What the dataset never owned is the PRESENTATION carried on those same objects: the per-series mark, its left/right axis binding, label, colour, stack, and the axis definitions' title, format, min, max, step, grid and position. Those are the author's, and they now merge onto the derived bindings by name/key match with the explicit binding winning — one merge function, not a spread per attribute. The split runs through the two binding keys: `ChartSeries.name` and `ChartAxis.field` name a column and stay with the dataset; everything else on the object travels.
|
|
159
|
+
|
|
160
|
+
This is objectui#2880's S2 rule, which PR #2883 landed in `ObjectChart` and which the dataset path never carried over. Dropping `ChartAxis.field` on the way through is what makes forwarding the axes safe rather than merely guarded: it is the one key by which an authored axis could have named a series, since the renderer synthesises series from `yAxis[].field` when a chart declares none.
|
|
161
|
+
|
|
162
|
+
Two consequences beyond the reported bug. A non-combo widget can now declare one line series and get the combo the renderer already knew how to derive from disagreeing series types. And a `compareTo` overlay inherits its own measure's mark and axis, so the comparison of a bar-on-the-left measure no longer draws as a line on the right the moment the chart becomes a combo.
|
|
163
|
+
|
|
164
|
+
Dashboards that never authored `chartConfig.series` or `chartConfig.yAxis` emit exactly what they emitted before.
|
|
165
|
+
|
|
166
|
+
- 0bf3f44: `DashboardRenderer`'s widget grid now passes only whitelisted DOM props to its container (objectui#4432)
|
|
167
|
+
|
|
168
|
+
`view:dashboard` resolves to this component, so `SchemaRenderer` handed it the dashboard node's own keys, the contents of the node's `props` container, the ARIA it resolved and the host's trailing props — and every key the component did not destructure was spread raw onto the grid container. React writes unknown lowercase attributes through in silence and stringifies object values, so the failure was invisible. Measured through the real SDUI path: **13 non-DOM attributes**, including `events="[object Object]"`, `props="[object Object]"` and a camelCase `arialabel` sitting next to the resolved `aria-label`, so the element carried each ARIA value twice under two spellings — one of them meaningless to assistive technology.
|
|
169
|
+
|
|
170
|
+
The container is now consume-or-whitelist per objectui#4425 phase 2: only `toDomProps`' output reaches the element, and it is spread FIRST so the component's own computed attributes stay authoritative. The resolved `aria-label` / `aria-describedby`, `role`, `id`, `tabIndex`, `className` and the `data-*` family still arrive — dropping them would have been an accessibility regression dressed as a leak fix, so the new pin asserts the delivered set exactly, not just the absent one. Both layout branches are covered: the responsive desktop grid and the mobile stack spread the same props onto the same host element.
|
|
171
|
+
|
|
172
|
+
Three behaviours move with the spread, all of them consequences of a trailing spread that used to override the component's own computed props:
|
|
173
|
+
|
|
174
|
+
- **`onClick` now has one carrier.** It is a declared DOM pass-through key AND this container computes a design-mode background handler, and the old spread let the incoming handler replace the computed one — so a host that passed `onClick` silently lost background deselection. Both run now, container affordance first. An authored non-function `onClick` (SDUI spells click behaviour `events: { onClick }`, which is data and is dropped) is ignored instead of handed to React, which used to throw on it.
|
|
175
|
+
- **An authored `style` no longer replaces the computed grid layout.** `style` is not in the SDUI pass-through set, and this container computes its own `gridTemplateColumns` / `gridAutoRows` / `gap`; an authored `style` used to overwrite all of it and collapse the grid.
|
|
176
|
+
- **An authored `data-user-actions` no longer overrides the value computed from the `userActions` prop.** The `data-*` family still passes the whitelist; only this one collision with a computed attribute resolves the other way now.
|
|
177
|
+
|
|
178
|
+
The injected `disabled` verdict is also dropped rather than forwarded. Nothing in this component ever read it: it only became a `disabled` attribute on a container element that has no such attribute, which is the leak, not a behaviour.
|
|
179
|
+
|
|
180
|
+
- eb7f586: Dashboard dataset measures follow the display locale (objectui#4566).
|
|
181
|
+
|
|
182
|
+
`formatMeasure` and `formatDimensionValue` in `@object-ui/core` formatted every
|
|
183
|
+
value with a bare `undefined` locale tag at all three of their `Intl` sites.
|
|
184
|
+
`undefined` is not "the user's locale", it is the MACHINE's — neither of the
|
|
185
|
+
repo's two locale channels. A German session read a dashboard KPI as `1,234.5`
|
|
186
|
+
next to a grid cell rendering the same number as `1.234,5`, and inverted
|
|
187
|
+
separators read as a different number, not as an unstyled one.
|
|
188
|
+
|
|
189
|
+
Both functions take the display locale as a new OPTIONAL LAST parameter, and
|
|
190
|
+
`DatasetWidget` threads `useDisplayLocale()` into every site it formats through:
|
|
191
|
+
the KPI, the grouped table's measure and dimension cells, and the cross-tab's
|
|
192
|
+
header labels and cells.
|
|
193
|
+
|
|
194
|
+
**English output does not move**, and that is the discriminator against the
|
|
195
|
+
sibling fix. These sites already went through `Intl` with default grouping, so
|
|
196
|
+
the only thing that changes is WHOSE locale is used:
|
|
197
|
+
|
|
198
|
+
| | before | after |
|
|
199
|
+
| ----------------- | ----------- | --------------------- |
|
|
200
|
+
| en, 1234.5 `0.0` | `1,234.5` | `1,234.5` (unchanged) |
|
|
201
|
+
| de, 1234.5 `0.0` | `1,234.5` | `1.234,5` |
|
|
202
|
+
| de, 1234.5 EUR | `€1,234.50` | `1.234,50 €` |
|
|
203
|
+
| de, 0.6083 `0.0%` | `60.8%` | `60,8%` |
|
|
204
|
+
|
|
205
|
+
Contrast objectui#4553, where `formatPercent` had never grouped at all and
|
|
206
|
+
moving en `1235%` → `1,235%` WAS the fix.
|
|
207
|
+
|
|
208
|
+
Omitting the new argument reproduces the previous output byte for byte, so
|
|
209
|
+
callers that do not thread a locale yet are unaffected.
|
|
210
|
+
|
|
211
|
+
Two behaviours are deliberately preserved rather than "improved" alongside the
|
|
212
|
+
locale fix, both measured:
|
|
213
|
+
|
|
214
|
+
- **Integers stay verbatim.** The integer branch renders no separator and no
|
|
215
|
+
decimal mark, so a locale has nothing to change there — and routing it through
|
|
216
|
+
`Intl` WOULD change it (a locale with its own numbering system re-digits it,
|
|
217
|
+
and `1e21` expands to 22 digits).
|
|
218
|
+
- **The percent sign stays a literal suffix.** `Intl`'s `style: 'percent'`
|
|
219
|
+
re-scales by 100, and that round trip loses precision at the top of the range
|
|
220
|
+
(en `100,000,000,000,000,000,000,000%` becomes
|
|
221
|
+
`99,999,999,999,999,990,000,000%`). The consequence — a German list cell
|
|
222
|
+
writing `1.234,5 %` with a no-break space where a dashboard measure writes
|
|
223
|
+
`1.234,5%` — is filed separately rather than smuggled in behind a locale fix.
|
|
224
|
+
|
|
225
|
+
`@object-ui/core` is `minor` because two of its ENTRY exports gained an optional
|
|
226
|
+
parameter (measured in the built `.d.ts`). `@object-ui/plugin-dashboard` is
|
|
227
|
+
`patch`: its published declarations are unchanged — `buildPivot`'s new optional
|
|
228
|
+
parameter is internal, as that function is not on the package's `exports`
|
|
229
|
+
surface.
|
|
230
|
+
|
|
231
|
+
- 54d34d2: A dashboard chart's null-value bucket now reads the app's language instead of the English `(None)`
|
|
232
|
+
|
|
233
|
+
`buildChartSeries` groups rows whose category value is `null` under a labelled bucket, so the group draws as a bar instead of vanishing off the axis (objectui#4466). The label comes from the caller: `@object-ui/core` is React-free, cannot read the locale bundle, and falls back to the English constant `(None)`. `ObjectChart` passes its resolved label and localizes; `DatasetWidget` called the same helper with no options, so a dashboard widget in a zh app labelled the bucket `(None)` while the standalone chart one panel over labelled it `(未指定)`. It now passes `chart.nullCategory` from the i18n channel, which every locale pack already carries.
|
|
234
|
+
|
|
235
|
+
The same label goes to `findChartSeriesRow`, and that half is what keeps the bar clickable. That helper is the inverse map behind segment-click drill-through: it compares the clicked category against its own copy of the bucket label, defaulting to the same English floor. Passing the localized label to only the forward call would draw a bar reading `(未指定)` while the drill matched `(None)` — the click resolves to no row and the drawer never opens, which is a worse outcome than the untranslated word this fixes. Both calls now read one binding, so they cannot drift apart.
|
|
236
|
+
|
|
237
|
+
Nothing else moves: non-null categories chart and drill exactly as before, an `en` app still reads `(None)` (now via its locale pack rather than the hardcoded floor), and a widget over data with no null group is untouched.
|
|
238
|
+
|
|
239
|
+
- ee7a68d: `DatasetWidget`'s option-color / dimension-label probe now rides the host's
|
|
240
|
+
authenticated fetch (`SchemaRendererContext.apiFetch`) instead of the bare global
|
|
241
|
+
`fetch`.
|
|
242
|
+
|
|
243
|
+
The one metadata read the effect makes — `GET /api/v1/meta/object/{object}` — went
|
|
244
|
+
out on the global `fetch`, so in a hosted console it skipped whatever the host
|
|
245
|
+
supplies on that channel (Authorization / tenant headers, base-URL rewrite,
|
|
246
|
+
draft-preview params). A bearer-token session carries its credential in a header
|
|
247
|
+
rather than a cookie, so `credentials: 'include'` alone left this read
|
|
248
|
+
unauthenticated. The effect is best-effort and swallows every failure, which made
|
|
249
|
+
the symptom silent: a dataset chart's semantic per-category colors and its
|
|
250
|
+
dimensions' value → label maps simply never applied, and the widget fell back to
|
|
251
|
+
the positional theme palette and the raw stored values on the axis.
|
|
252
|
+
|
|
253
|
+
Standalone embeds are unaffected — with no provider (or a provider that supplies no
|
|
254
|
+
`apiFetch`) the probe still uses the global `fetch`, the same documented fallback
|
|
255
|
+
`useRecordEditable` and `provider: 'api'` view sources use.
|
|
256
|
+
|
|
257
|
+
This is the `plugin-dashboard` twin of the same fix made to `plugin-charts`'
|
|
258
|
+
`ObjectChart`.
|
|
259
|
+
|
|
260
|
+
- 436681e: fix(dashboard): resolve a dotted dimension's labels on table and pivot dataset widgets
|
|
261
|
+
|
|
262
|
+
A dataset widget's client-side dimension-label safety net returned early for
|
|
263
|
+
`table` / `pivot` / metric widgets, so a DOTTED dimension (`crm_account.industry`)
|
|
264
|
+
rendered the raw stored enum (`education`) there — the same symptom objectui#4053
|
|
265
|
+
fixed for charts, on the widget types its fix did not reach.
|
|
266
|
+
|
|
267
|
+
The early return stays for LOCAL dimensions, which is what made it correct in the
|
|
268
|
+
first place: on a table the server resolves those labels (ADR-0021), so running
|
|
269
|
+
the client net for them would be a second resolution of an already-resolved
|
|
270
|
+
value. It now opens only for dotted paths — the case the server is silent on too —
|
|
271
|
+
reusing the existing `resolveDimensionFieldOptions` walk unchanged, multi-hop
|
|
272
|
+
paths included. A table with no dotted dimension resolves nothing and issues no
|
|
273
|
+
metadata read at all, so those widgets render byte-identically.
|
|
274
|
+
|
|
275
|
+
A pivot's marginal totals take the same relabel as its rows, because their bucket
|
|
276
|
+
ids are re-derived from the dimension values that the headers are built from; the
|
|
277
|
+
CSV export follows the table's cells for the same reason. Drill-through still
|
|
278
|
+
filters by the stored value — the relabel preserves row order and count, so the
|
|
279
|
+
raw rows it indexes stay aligned.
|
|
280
|
+
|
|
281
|
+
Metric widgets are unaffected by design: that branch renders one measure value
|
|
282
|
+
and its header label and puts no dimension value on screen, so it has nothing to
|
|
283
|
+
resolve.
|
|
284
|
+
|
|
285
|
+
- 613b167: A dataset dimension on a dotted relationship path now renders its option labels instead of the raw stored enum
|
|
286
|
+
|
|
287
|
+
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.
|
|
288
|
+
|
|
289
|
+
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.
|
|
290
|
+
|
|
291
|
+
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.
|
|
292
|
+
|
|
293
|
+
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.
|
|
294
|
+
|
|
295
|
+
Applies to both surfaces that carried this lookup: dashboard dataset widgets (`DatasetWidget`) and the chart view's dataset path (`ObjectChart`).
|
|
296
|
+
|
|
297
|
+
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.
|
|
298
|
+
|
|
299
|
+
- bb68488: An inline per-locale label now renders its locale's string at the thirteen read sites the `@objectstack/spec` 17.0.0-rc.6 bump exposed
|
|
300
|
+
|
|
301
|
+
rc.6 widened `I18nLabel` from `string` to `string | Record<string, string>`, so an author may write `label: { en: 'Owner', 'zh-CN': '负责人' }` anywhere the spec accepts a display label. PR #4169 repaired eight such sites; these thirteen were invisible to it because the five packages involved build through vite/rolldown, so `turbo run build` never type-checks their sources — only `turbo run type-check` does. All thirteen are now resolved through a shared resolver against a real locale, and `turbo run type-check` is 78/78 with zero errors.
|
|
302
|
+
|
|
303
|
+
| package | what an author can now write and see |
|
|
304
|
+
| ----------------------------- | --------------------------------------------------------------------------------------- |
|
|
305
|
+
| `@object-ui/layout` | `NavigationArea.label` — the sidebar area switcher's button and its tooltip |
|
|
306
|
+
| `@object-ui/plugin-list` | `ViewTab.label` — the inline pill row, and the mobile dropdown's trigger and menu items |
|
|
307
|
+
| `@object-ui/plugin-dashboard` | `DashboardWidget.title` — the widget card heading and its `title` attribute |
|
|
308
|
+
| `@object-ui/plugin-designer` | `DashboardWidget.title` — the widget card and the preview tile |
|
|
309
|
+
| `@object-ui/app-shell` | `ActionParam.label` **and** each `ActionParam.options[].label` |
|
|
310
|
+
|
|
311
|
+
**Patch, not minor, in every case: no public surface changes meaning.** Every entry above is a read site that previously could only be reached with a value the type system rejected, so no caller's working code changes behaviour. `@object-ui/app-shell` is the only package with an exported-type change and it is purely additive on the authoring side — `RawActionParam.label` and `RawActionParam.options[].label` widen to `I18nLabel` (they accept strictly more), `ResolveActionParamsContext` gains an optional `locale`, and the new `RawActionParamOption` names the authoring shape that was previously spelled with the resolved one. What `resolveActionParams` **emits** is unchanged: `ActionParamDef.label` and its options' labels are still plain `string`s.
|
|
312
|
+
|
|
313
|
+
Two consequences worth knowing:
|
|
314
|
+
|
|
315
|
+
- **The dashboard designer's title input is deliberately read-only for a map-valued title.** Resolving a per-locale map into a single-line input and writing `e.target.value` back would collapse every other locale on the first keystroke, so the write is guarded and an inline map survives an unrelated edit-and-save round trip untouched — the same conservative branch #4169 took for `DashboardWidgetInspector`. What Studio should actually offer for authoring a per-locale label is objectui#4163 part 2, which is unclaimed and pending design.
|
|
316
|
+
- **`@object-ui/layout` resolves at the spec's `en` default, not the viewer's language.** That package carries no i18n dependency by design (its whole i18n story is injection), and `AppSchemaRendererProps` exposes no locale to thread. The choice and what would change it are documented at the call site.
|
|
317
|
+
|
|
318
|
+
- 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
|
|
319
|
+
|
|
320
|
+
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.
|
|
321
|
+
|
|
322
|
+
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.
|
|
323
|
+
|
|
324
|
+
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.
|
|
325
|
+
|
|
326
|
+
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.
|
|
327
|
+
|
|
328
|
+
- 7e4f0e5: fix(dashboard,i18n): KPI cards and dashboard filters resolve authored labels instead of dropping them (#4032)
|
|
329
|
+
|
|
330
|
+
A `type: 'metric'` dashboard widget rendered raw English while every other widget
|
|
331
|
+
type on the same dashboard rendered the translation, and dashboard filter chips
|
|
332
|
+
rendered `[object Object]` or the raw stored value. Both come from the same
|
|
333
|
+
cause: authored labels reaching a render site that could not read the
|
|
334
|
+
vocabulary `@objectstack/spec` actually admits.
|
|
335
|
+
|
|
336
|
+
- **KPI cards rejoin the widget translation channel.** The self-contained
|
|
337
|
+
`metric` branch built its own label from the raw `widget.title`, so the
|
|
338
|
+
`{ns}.dashboards.{dash}.widgets.{id}.title` value the renderer had already
|
|
339
|
+
resolved was computed and thrown away. It now reads that channel like every
|
|
340
|
+
other widget header.
|
|
341
|
+
- **The three private `resolveLabel` copies** (`DashboardRenderer`,
|
|
342
|
+
`MetricWidget`, `MetricCard`) are gone. Each read the retired
|
|
343
|
+
`{ key, defaultValue }` key-reference form and ended `defaultValue || key`, so
|
|
344
|
+
handed the inline per-locale map the spec admits today they returned nothing —
|
|
345
|
+
a KPI card with a map title rendered the literal string `metric`. All three
|
|
346
|
+
now use `pickLocalized`, the resolver already used for this vocabulary
|
|
347
|
+
elsewhere in the package.
|
|
348
|
+
- **Dashboard filter labels and static option labels resolve per locale.**
|
|
349
|
+
`DashboardFilterDef.label` widens to `string | I18nLabel`, the filter bar
|
|
350
|
+
resolves before rendering (fixing `[object Object]: All` in the trigger, and
|
|
351
|
+
in `aria-label` / `placeholder`), and the `def.label || def.name` gate now
|
|
352
|
+
tests the RESOLVED string — an object is always truthy, so it never reached
|
|
353
|
+
the fallback before.
|
|
354
|
+
- **Option labels are no longer discarded.** `normalizeFilterOptions` coerced a
|
|
355
|
+
map label to the raw stored value in every locale, English included, so
|
|
356
|
+
`{ value: 'domestic', label: { en: 'Domestic', … } }` displayed as `domestic`.
|
|
357
|
+
The pair shape is still normalized; the label vocabulary is preserved for the
|
|
358
|
+
render side to resolve.
|
|
359
|
+
- **`DashboardComponentSchema.globalFilters` is bound to the spec's
|
|
360
|
+
`GlobalFilter`** instead of restated by hand. The restatement was both too
|
|
361
|
+
narrow (`label?: string`, which is what made these read sites invisible to
|
|
362
|
+
`tsc`) and too wide (it declared a bare-string option shorthand the spec
|
|
363
|
+
rejects at publish).
|
|
364
|
+
|
|
365
|
+
Plain-string labels are unaffected and render byte-identically.
|
|
366
|
+
|
|
367
|
+
- 306c101: KPI cards no longer write their own schema onto the DOM — `MetricWidget` and
|
|
368
|
+
`MetricCard` keep `SchemaRenderer`'s schema-shaped props out of the `...props`
|
|
369
|
+
spread (objectui#4357).
|
|
370
|
+
|
|
371
|
+
Both components are two things at once: an SDUI block reached through
|
|
372
|
+
`SchemaRenderer`, and a plain React component a host may render directly. The
|
|
373
|
+
React half wants a `...props` spread on its root so callers can pass `aria-*`,
|
|
374
|
+
`data-*`, `id`, `role`. The SDUI half means that spread also received the node's
|
|
375
|
+
own metadata — and React writes unknown lowercase attributes straight to the DOM,
|
|
376
|
+
stringifying object values. Every KPI card therefore carried
|
|
377
|
+
`schema="[object Object]"`, and a widget authored with events, a binding or a
|
|
378
|
+
props container carried `events="[object Object]"`, `bind="data.revenue"` and
|
|
379
|
+
`props="[object Object]"` beside it.
|
|
380
|
+
|
|
381
|
+
Seven props were measured arriving at the call site that are not HTML attribute
|
|
382
|
+
names — `schema`, `events`, `props`, `bind`, `ariaLabel`, `ariaDescribedBy` (the
|
|
383
|
+
last two are the camelCase authored forms of ARIA the renderer already emits in
|
|
384
|
+
their dashed spelling) and `dataSource`. They are destructured out; the spread
|
|
385
|
+
survives untouched for everything that IS a DOM attribute: `id`, `name`, `role`,
|
|
386
|
+
`disabled`, `aria-*`, `data-*`, `className`. Nothing else about the render moves
|
|
387
|
+
— no text, no class, no element.
|
|
388
|
+
|
|
389
|
+
`dataSource` is the one that only a live dashboard shows. It is not a schema key
|
|
390
|
+
(the renderer strips the schema's own `dataSource` binding by name); it is the
|
|
391
|
+
injected adapter `DashboardRenderer` hands its `SchemaRenderer` call, which
|
|
392
|
+
arrives through the renderer's trailing props. Every fixture in this package
|
|
393
|
+
renders without an adapter, so it read `undefined` and wrote nothing — while
|
|
394
|
+
every deployment that actually loads data put `datasource="[object Object]"` on
|
|
395
|
+
the card. The pin renders a dashboard with an adapter so the case that only
|
|
396
|
+
production had is now a test.
|
|
397
|
+
|
|
398
|
+
The cost of this was never visible; it was that the defect poisoned the
|
|
399
|
+
assertion this area attracts. objectui#4163 pins
|
|
400
|
+
`not.toContain('[object Object]')` on the dashboard grid, and objectui#4032
|
|
401
|
+
wanted the same pin on the metric path but could not write it: the card carried
|
|
402
|
+
the attribute before and after any i18n fix, so the container assertion was red
|
|
403
|
+
for a reason unrelated to labels and the tempting repair was to loosen it. That
|
|
404
|
+
suite asserted on the card heading instead, with a comment. The workaround is
|
|
405
|
+
now removed and the container assertion is back.
|
|
406
|
+
|
|
407
|
+
The exported `MetricWidgetProps` / `MetricCardProps` interfaces are unchanged —
|
|
408
|
+
the components' accepted props widen only by the optional, ignored
|
|
409
|
+
`SchemaHostProps` keys, so no consumer type narrows.
|
|
410
|
+
|
|
411
|
+
- 45e1949: Numbers render in the user's locale, and a `Field.number` year is no longer `2,026`
|
|
412
|
+
|
|
413
|
+
Every numeric field the console rendered went through an `Intl.NumberFormat` built with the locale hardcoded to `en-US` and `useGrouping` never set. Two defects rode in that one construction: a `zh-CN` or `de-DE` console still grouped and pointed decimals the US way, and a four-digit **year** stored as `Field.number({ scale: 0 })` rendered as `2,026` — in every locale, with no field property able to turn it off. Apps had been converting year columns to `Field.text` to escape it, permanently trading numeric comparison, range filters and dataset dimension types for a display detail.
|
|
414
|
+
|
|
415
|
+
The construction had been copied into five places — the number cell renderer, the currency cell renderer, the `CurrencyField` widget, the compact `formatNumber` helper, and the dashboard `MetricWidget` — so fixing any one surface never changed the answer. They now share one formatter, `formatDisplayNumber` in `@object-ui/i18n`, which owns the locale and the grouping policy together, plus one locale resolver, `useDisplayLocale`.
|
|
416
|
+
|
|
417
|
+
`useDisplayLocale` composes the two locale channels this repo already had rather than adding a third: the tenant's regional default (`useLocalization().locale`, ADR-0053) when an org has configured one, otherwise the active UI language (`useObjectTranslation().language`) so grouping and decimal marks follow a language switch. That second step is what covers the case the report was measured in — a fresh database, where the tenant localization endpoint has no locale to give.
|
|
418
|
+
|
|
419
|
+
Grouping is now suppressed when a field declares `scale: 0` and carries no currency, which is what makes years, fiscal periods and other ordinals render plainly. This is an **interim default** with an accepted cost: a large scale-0 _count_ loses its separators too. It holds only until the spec gains an authorable presentation hint, which is being specified separately, contract-first; when that lands it overrides this heuristic.
|
|
420
|
+
|
|
421
|
+
Three surfaces deliberately keep their separators, because a zero-decimal display there does not come from a field declaration: the dashboard `MetricWidget` (its decimals are parsed from a numeral.js format pattern, and its own contract calls the separators load-bearing — "`1,930,000` not `1930000`"), the `element:number` aggregate renderer, and every currency path including amounts whose currency code could not be resolved. An **undeclared** `scale` also keeps grouping — absent means "decimals unknown", not "integer".
|
|
422
|
+
|
|
423
|
+
`formatCurrency`, `formatCompactCurrency` and `formatNumber` each take a new optional trailing `locale` argument. Existing calls are unaffected; omitting it now follows the runtime default rather than forcing US conventions.
|
|
424
|
+
|
|
425
|
+
- 844ed3a: Dashboard global filters sourced from `optionsFrom` now commit the RAW value instead of the display label.
|
|
426
|
+
|
|
427
|
+
The option source is a server GROUP BY whose response carries both forms of every grouped value: `rows` holds the resolved display labels (`{status: 'In Review'}`) and the index-aligned `drillRawRows` holds the raw stored values (`{status: 'in_review'}`). `DashboardFilterBar` read the value off `rows`, so picking an option broadcast a label no record carries into every bound widget's `runtimeFilter` and each widget repainted to "No rows". Options are now paired index-wise — value from `drillRawRows`, label from the displayed row — mirroring how the drill path has always read the same response. The trigger still displays the label, and statically declared `options` are unaffected. When the raw rows are absent, disagree in length with `rows`, or carry no such field, the previous read is kept rather than guessing at a pairing.
|
|
428
|
+
|
|
429
|
+
- 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
|
|
430
|
+
|
|
431
|
+
objectstack#5473 / objectstack#5665 replaced the pivot's delimiter-joined ids
|
|
432
|
+
with `JSON.stringify`, because every delimiter that had been tried — an empty
|
|
433
|
+
string, a plain space, a control character — assumed the data would not contain
|
|
434
|
+
it, and each assumption failed on ordinary data. This closes the last place the
|
|
435
|
+
same assumption survived: the ids were JSON, but the VALUES fed into them were
|
|
436
|
+
spelled `String(row[d] ?? '∅')`, so an absent dimension value became the
|
|
437
|
+
ordinary string `"∅"` and shared a bucket with a row whose value literally is
|
|
438
|
+
that character (U+2205). One bucket, later row overwriting the earlier one — the
|
|
439
|
+
cell showed a different row's measure, the overwritten row was unreachable, and
|
|
440
|
+
drill-through followed the same wrong index into the wrong records, all without
|
|
441
|
+
an error. The trigger requires that character to appear as a dimension value, so
|
|
442
|
+
this is the assumption being removed rather than a defect users hit today.
|
|
443
|
+
|
|
444
|
+
An empty value now encodes as JSON `null`, which `JSON.stringify` renders as a
|
|
445
|
+
bare `null` that no string can spell. The normalization lives in
|
|
446
|
+
`@object-ui/core` as `pivotDimensionValue` (absent ⇒ `null`, everything else ⇒
|
|
447
|
+
its string form) rather than at each call site, because a placeholder spelled by
|
|
448
|
+
a caller is a placeholder that can collide again — which is exactly how this one
|
|
449
|
+
survived the previous fix. `pivotBucketId` accepts `Array<string | null>`
|
|
450
|
+
accordingly; that is a widening, so existing callers passing `string[]` are
|
|
451
|
+
unaffected.
|
|
452
|
+
|
|
453
|
+
Both renderers' bucket keys move together, which the fix requires: a bucket id
|
|
454
|
+
and the subtotal map keyed by it are built from the same expression, so changing
|
|
455
|
+
one alone would split the headers while the subtotal map still merged, landing
|
|
456
|
+
every column subtotal under the wrong header. In `plugin-dashboard`'s
|
|
457
|
+
`DatasetWidget` that is the row bucket id, the column bucket id, the cell key,
|
|
458
|
+
and both the `rowTotalById` and `colTotalById` lookups; in `plugin-report`'s
|
|
459
|
+
`DatasetReportRenderer` the single `bucketId` helper already feeds all five.
|
|
460
|
+
|
|
461
|
+
The dashboard's column bucket id also stops being a bare string and becomes a
|
|
462
|
+
one-element tuple through the same shared encoder. It was the one id in the
|
|
463
|
+
family still built by hand, on the reasoning that a single value needs no
|
|
464
|
+
boundary — true of the boundary, false of everything else the encoder does, and
|
|
465
|
+
it is why the across axis kept carrying this collision after the row ids were
|
|
466
|
+
fixed.
|
|
467
|
+
|
|
468
|
+
No display change: these placeholders only ever entered ids, never labels. An
|
|
469
|
+
unset dimension still renders through `formatDimensionValue` exactly as before,
|
|
470
|
+
and data containing neither an absent value nor that character buckets
|
|
471
|
+
identically — the ids are opaque lookup keys, never parsed back into a value,
|
|
472
|
+
never shown, never persisted.
|
|
473
|
+
|
|
474
|
+
- a3ae404: fix(components,plugin-dashboard): a static-data `table` widget renders instead of crashing
|
|
475
|
+
|
|
476
|
+
A dashboard widget authored as `{ type: 'table', options: { data: [ … ] } }` fell into the
|
|
477
|
+
error boundary with "Maximum update depth exceeded" the moment its tile re-rendered, while
|
|
478
|
+
every chart family on the identical static surface rendered clean.
|
|
479
|
+
|
|
480
|
+
- `data-table` no longer re-renders itself to death. Its `columns` / `data` fallbacks are
|
|
481
|
+
module-scope empties instead of per-render array literals, and the prop→state column sync
|
|
482
|
+
re-seeds on a value change rather than on a new identity — so a consumer that derives its
|
|
483
|
+
columns each render (which both dashboard surfaces do) costs the table nothing.
|
|
484
|
+
- Both dashboard surfaces now give the static table the `columns` key `DataTableSchema`
|
|
485
|
+
requires, derived from the rows when the author declared none — the same derivation the
|
|
486
|
+
`provider: 'object'` half of the widget family already performed. Previously such a table
|
|
487
|
+
drew one empty row per record: no headers, no cells.
|
|
488
|
+
- `DashboardGridLayout` reads an authored `options.data` ARRAY for its static table, which
|
|
489
|
+
its `widgetData?.items` expression resolved to `[]`. `DashboardRenderer` had the arm all
|
|
490
|
+
along.
|
|
491
|
+
|
|
492
|
+
- 3f5f87c: `SchemaRenderer` states its real contract — a typed, required `schema` and a deliberate forwarding surface
|
|
493
|
+
|
|
494
|
+
`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.
|
|
495
|
+
|
|
496
|
+
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.
|
|
497
|
+
|
|
498
|
+
**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>>`.
|
|
499
|
+
|
|
500
|
+
`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.
|
|
501
|
+
|
|
502
|
+
**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.
|
|
503
|
+
|
|
504
|
+
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.
|
|
505
|
+
|
|
506
|
+
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.
|
|
507
|
+
|
|
508
|
+
- Updated dependencies [0e67b53]
|
|
509
|
+
- Updated dependencies [ceccdcf]
|
|
510
|
+
- Updated dependencies [d6e5124]
|
|
511
|
+
- Updated dependencies [debad27]
|
|
512
|
+
- Updated dependencies [dc2aa3e]
|
|
513
|
+
- Updated dependencies [ee66e2e]
|
|
514
|
+
- Updated dependencies [e2e6360]
|
|
515
|
+
- Updated dependencies [ee26e65]
|
|
516
|
+
- Updated dependencies [5900ac5]
|
|
517
|
+
- Updated dependencies [932cbcd]
|
|
518
|
+
- Updated dependencies [734d186]
|
|
519
|
+
- Updated dependencies [f650253]
|
|
520
|
+
- Updated dependencies [3d9769a]
|
|
521
|
+
- Updated dependencies [8f85f8b]
|
|
522
|
+
- Updated dependencies [d0c3b26]
|
|
523
|
+
- Updated dependencies [3fc2971]
|
|
524
|
+
- Updated dependencies [aca27fa]
|
|
525
|
+
- Updated dependencies [dde7283]
|
|
526
|
+
- Updated dependencies [f7c6430]
|
|
527
|
+
- Updated dependencies [4dadf0d]
|
|
528
|
+
- Updated dependencies [ae10a01]
|
|
529
|
+
- Updated dependencies [0f21348]
|
|
530
|
+
- Updated dependencies [d2e2caf]
|
|
531
|
+
- Updated dependencies [92876f0]
|
|
532
|
+
- Updated dependencies [f279deb]
|
|
533
|
+
- Updated dependencies [4b70d28]
|
|
534
|
+
- Updated dependencies [eb7f586]
|
|
535
|
+
- Updated dependencies [e901131]
|
|
536
|
+
- Updated dependencies [ebb4e0e]
|
|
537
|
+
- Updated dependencies [3a9021e]
|
|
538
|
+
- Updated dependencies [d9d3463]
|
|
539
|
+
- Updated dependencies [2a40f69]
|
|
540
|
+
- Updated dependencies [bec3e14]
|
|
541
|
+
- Updated dependencies [613b167]
|
|
542
|
+
- Updated dependencies [b4d3c22]
|
|
543
|
+
- Updated dependencies [1f9b905]
|
|
544
|
+
- Updated dependencies [8f60d73]
|
|
545
|
+
- Updated dependencies [cb13400]
|
|
546
|
+
- Updated dependencies [828549a]
|
|
547
|
+
- Updated dependencies [e1ade8f]
|
|
548
|
+
- Updated dependencies [bc64bfe]
|
|
549
|
+
- Updated dependencies [abb0f81]
|
|
550
|
+
- Updated dependencies [38ab505]
|
|
551
|
+
- Updated dependencies [3e19fe7]
|
|
552
|
+
- Updated dependencies [bb58d1d]
|
|
553
|
+
- Updated dependencies [433ff9f]
|
|
554
|
+
- Updated dependencies [5cc847c]
|
|
555
|
+
- Updated dependencies [e7663f2]
|
|
556
|
+
- Updated dependencies [fa21254]
|
|
557
|
+
- Updated dependencies [33c32bf]
|
|
558
|
+
- Updated dependencies [66fb4fa]
|
|
559
|
+
- Updated dependencies [b953a97]
|
|
560
|
+
- Updated dependencies [d7f3e30]
|
|
561
|
+
- Updated dependencies [6d641c9]
|
|
562
|
+
- Updated dependencies [7e4f0e5]
|
|
563
|
+
- Updated dependencies [a84385b]
|
|
564
|
+
- Updated dependencies [45e1949]
|
|
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 [bfdf3d4]
|
|
574
|
+
- Updated dependencies [bb68488]
|
|
575
|
+
- Updated dependencies [c0f9a4b]
|
|
576
|
+
- Updated dependencies [b1e42d0]
|
|
577
|
+
- Updated dependencies [2459a3e]
|
|
578
|
+
- Updated dependencies [ac853ce]
|
|
579
|
+
- Updated dependencies [fa51109]
|
|
580
|
+
- Updated dependencies [d6aa172]
|
|
581
|
+
- Updated dependencies [fe52a04]
|
|
582
|
+
- Updated dependencies [d46f9b8]
|
|
583
|
+
- Updated dependencies [3f5f87c]
|
|
584
|
+
- Updated dependencies [2fea4d2]
|
|
585
|
+
- Updated dependencies [f5e1143]
|
|
586
|
+
- Updated dependencies [7f1cb33]
|
|
587
|
+
- Updated dependencies [f148a64]
|
|
588
|
+
- Updated dependencies [bb68488]
|
|
589
|
+
- Updated dependencies [2e3b0c0]
|
|
590
|
+
- Updated dependencies [9461dd3]
|
|
591
|
+
- Updated dependencies [78fa331]
|
|
592
|
+
- Updated dependencies [47f551b]
|
|
593
|
+
- Updated dependencies [31ab1ac]
|
|
594
|
+
- Updated dependencies [0082db8]
|
|
595
|
+
- Updated dependencies [ab04728]
|
|
596
|
+
- Updated dependencies [5bf09fd]
|
|
597
|
+
- Updated dependencies [06915b0]
|
|
598
|
+
- Updated dependencies [ff84b05]
|
|
599
|
+
- @object-ui/i18n@17.5.0
|
|
600
|
+
- @object-ui/react@17.5.0
|
|
601
|
+
- @object-ui/components@17.5.0
|
|
602
|
+
- @object-ui/core@17.5.0
|
|
603
|
+
- @object-ui/fields@17.5.0
|
|
604
|
+
- @object-ui/types@17.5.0
|
|
605
|
+
|
|
606
|
+
## 17.4.0
|
|
607
|
+
|
|
608
|
+
### Patch Changes
|
|
609
|
+
|
|
610
|
+
- 4bc6c23: Converge dashboard widget `compareTo` on the executor's `{ kind, dimension? }` contract, and make the dataset path actually render a comparison
|
|
611
|
+
|
|
612
|
+
`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:
|
|
613
|
+
|
|
614
|
+
- `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.
|
|
615
|
+
- `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.
|
|
616
|
+
- **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.
|
|
617
|
+
- 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.
|
|
618
|
+
|
|
619
|
+
- 230ffd8: Dashboard metadata's `chartConfig` presentation keys now take effect for the first time
|
|
620
|
+
|
|
621
|
+
`DashboardWidgetSchema.chartConfig` is declared as the full spec
|
|
622
|
+
`ChartConfigSchema`, but the ADR-0021 dataset path lowered exactly one key onto
|
|
623
|
+
the chart renderer: `showLegend` (objectui#3135). Everything else an author wrote
|
|
624
|
+
there — the chart's own `title`/`subtitle`, the accessibility `description`, an
|
|
625
|
+
explicit plot `height`, a `colors` palette or per-category colour map,
|
|
626
|
+
`showDataLabels`, `annotations`, `interaction` — parsed as valid metadata,
|
|
627
|
+
reached `DatasetWidget`, and was dropped before the chart schema was built. The
|
|
628
|
+
underlying chart block draws all of them; only the dashboard's hand-off was
|
|
629
|
+
missing.
|
|
630
|
+
|
|
631
|
+
`DatasetWidget` now lowers each of those keys, on two mechanical criteria, both
|
|
632
|
+
of which have to hold:
|
|
633
|
+
|
|
634
|
+
1. **The chart block draws it end to end on this path.** `{ type: 'chart' }`
|
|
635
|
+
resolves to `ChartRenderer` → `AdvancedChartImpl`, which draws
|
|
636
|
+
`title`/`subtitle` above the plot, turns `description` into the chart
|
|
637
|
+
container's `role="img"` + `aria-label`, applies `height` as that container's
|
|
638
|
+
inline height, paints `colors`, prints `showDataLabels` as per-point labels,
|
|
639
|
+
draws `annotations` as reference lines/bands and honours `interaction` as the
|
|
640
|
+
tooltip toggle plus the range selector. Each is pinned at the DOM level, so a
|
|
641
|
+
key is never forwarded to a prop that ignores it.
|
|
642
|
+
2. **It does not fight the dataset derivation.** `xAxis`, `yAxis` and `series`
|
|
643
|
+
are derived from the widget's dataset selection, so an authored one would
|
|
644
|
+
shadow the derived binding and blank the chart; they stay unforwarded, as does
|
|
645
|
+
`type` (the widget's own `type` already picks the chart family). `aria` stays
|
|
646
|
+
unforwarded too, for the other reason: nothing on this path reads it.
|
|
647
|
+
|
|
648
|
+
`colors` is split the way the react tier already splits it, because the two arms
|
|
649
|
+
reach the renderer through different props: a `string[]` is the positional
|
|
650
|
+
palette, a `{ value: color }` record is a per-category map merged over the
|
|
651
|
+
category dimension's own option colours.
|
|
652
|
+
|
|
653
|
+
**Behaviour-opening surface.** A dashboard that already wrote any of these keys
|
|
654
|
+
goes from having them ignored to having them applied — the point of the change,
|
|
655
|
+
but visible: a widget that declared `chartConfig.title` now shows that title
|
|
656
|
+
inside the plot area (in addition to the widget card's own `title`, which is a
|
|
657
|
+
separate key), one that declared `height` no longer fills its card, one that
|
|
658
|
+
declared `colors` stops using the theme palette, and `showDataLabels`,
|
|
659
|
+
`annotations` and `interaction.brush` start drawing. Widgets with no
|
|
660
|
+
`chartConfig`, or with only `showLegend`, render exactly as before: undeclared
|
|
661
|
+
keys are never emitted, so the renderer's own defaults stay in charge.
|
|
662
|
+
|
|
663
|
+
Part of objectstack#5175 (the enforce half); the narrowing half — what to do
|
|
664
|
+
about `aria`, and about `xAxis`/`yAxis`/`series` being declared on a surface that
|
|
665
|
+
derives them — is still open there.
|
|
666
|
+
|
|
667
|
+
- c4c0ac8: Dataset-bound metric cards honour their declared `colorVariant` (objectui#3359, objectstack#5010 ruling B)
|
|
668
|
+
|
|
669
|
+
`DashboardWidgetSchema.widgets[].colorVariant` has been spec-declared, offered by
|
|
670
|
+
every authoring surface (the widget inspector, the dashboard editor, the config
|
|
671
|
+
panel) and authored **16 times** in shipped metadata — `system_overview` ×7 in
|
|
672
|
+
`platform-objects`, app-showcase's `ops-dashboard` / `revenue-pulse` ×9 — with
|
|
673
|
+
every one of those a `type: 'metric'` widget bound to a dataset. None of them
|
|
674
|
+
ever rendered a colour.
|
|
675
|
+
|
|
676
|
+
The reason is structural rather than a missing branch: `dataset` is **required**
|
|
677
|
+
on `DashboardWidgetSchema`, so every legal widget reaches `DatasetWidget` through
|
|
678
|
+
one of `DashboardRenderer`'s two dispatch sites, and `DatasetWidget` read the key
|
|
679
|
+
nowhere. Only the inline (`object` + `valueField`) path had a colour affordance,
|
|
680
|
+
via the `...options` spread into `MetricWidget` — a path the current schema
|
|
681
|
+
cannot produce. Declared, authored, offered in the designer, and inert: the
|
|
682
|
+
renderer painted all sixteen the same.
|
|
683
|
+
|
|
684
|
+
The metric card now maps the declaration onto the accent system this package
|
|
685
|
+
already has, instead of a second one:
|
|
686
|
+
|
|
687
|
+
- the vocabulary is the spec's `WidgetColorVariantSchema` enum, read from the
|
|
688
|
+
spec **in a test** rather than restated in prose — `default`, `blue`, `teal`,
|
|
689
|
+
`orange`, `purple`, `success`, `warning`, `danger`;
|
|
690
|
+
- the accent lands on the big number, the way `MetricWidget`'s chrome-less
|
|
691
|
+
`bare` layout carries it, because a dataset-bound metric renders no icon chip
|
|
692
|
+
and no card of its own. A dataset-bound KPI and an inline `bare` KPI declaring
|
|
693
|
+
the same variant now read the same;
|
|
694
|
+
- the two class tables both layouts use moved into one shared module
|
|
695
|
+
(`colorVariants.ts`) rather than being copied — the designer's swatch picker
|
|
696
|
+
already calls itself a mirror of "the renderer's colorVariant tokens", and a
|
|
697
|
+
second copy of a palette is how a declared-but-unenforced key becomes the
|
|
698
|
+
harder bug: a key declared two disagreeing ways.
|
|
699
|
+
|
|
700
|
+
Nothing changes for a widget that declares no `colorVariant`: its markup is
|
|
701
|
+
pinned byte-for-byte against the pre-change render, as is the enum's own
|
|
702
|
+
`'default'` (its name for "no accent"). Off-spec tokens — including the swatch
|
|
703
|
+
picker's three display-only aliases `green` / `red` / `amber`, which exist so a
|
|
704
|
+
legacy stored value can still be drawn as a swatch — get no accent and no
|
|
705
|
+
aliasing here: the spec enum rejects them where metadata is authored and
|
|
706
|
+
published, and teaching the renderer a second spelling would hand AI-authored
|
|
707
|
+
metadata a dialect the contract does not have.
|
|
708
|
+
|
|
709
|
+
- 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
|
|
710
|
+
`list-view` — and `element:record_picker` stops discarding `view`
|
|
711
|
+
(objectstack#6953).
|
|
712
|
+
|
|
713
|
+
objectstack#5576 wired the spec's per-element data binding
|
|
714
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
|
|
715
|
+
the same declaration inert on every other page component. Two gaps remained, and
|
|
716
|
+
both were silent:
|
|
717
|
+
|
|
718
|
+
- **`element:record_picker` read four of the five keys and dropped `view`.** So
|
|
719
|
+
`dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
720
|
+
built a picker over EVERY account instead of the rows the saved view selects.
|
|
721
|
+
Nothing threw and nothing rendered an error; the option list was simply wider
|
|
722
|
+
than what was authored, which also means a user could select a record the page
|
|
723
|
+
said was out of scope.
|
|
724
|
+
- **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
|
|
725
|
+
`object-chart` / `object-metric` / `record:related_list` read none of it.**
|
|
726
|
+
Each gates its fetch on its own `objectName`, and nothing mapped
|
|
727
|
+
`dataSource.object` onto it, so a page written the way the spec documents
|
|
728
|
+
rendered an empty grid / a field-less form / a board with no cards / an empty
|
|
729
|
+
month / an empty chart / a static metric number — with no request and no
|
|
730
|
+
diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
731
|
+
objectstack#4413 shape.
|
|
732
|
+
|
|
733
|
+
Composition follows objectstack#5576's landed semantics unchanged on every block:
|
|
734
|
+
a named saved view supplies the baseline, a key written on the component itself
|
|
735
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
736
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
737
|
+
a `view` name that does not resolve renders a configuration error instead of
|
|
738
|
+
degrading to the object's full scope.
|
|
739
|
+
|
|
740
|
+
- `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
|
|
741
|
+
and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
|
|
742
|
+
block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
|
|
743
|
+
for the two non-final states. One precedence table for all blocks rather than
|
|
744
|
+
one copy per block — that copy is how "additional filter criteria" would have
|
|
745
|
+
become two dialects.
|
|
746
|
+
- A mapping names **only** keys its block genuinely reads. A composed value
|
|
747
|
+
written onto a key the block ignores would be accepted and dropped, which is
|
|
748
|
+
the defect being removed, one layer deeper — so a kanban's swimlane `columns`
|
|
749
|
+
never receive a view's field list, and a block with no row cap leaves `limit`
|
|
750
|
+
unmapped. The per-block coverage table, including two residual gaps that are
|
|
751
|
+
named rather than papered over, is in `content/docs/guide/data-source.md`.
|
|
752
|
+
|
|
753
|
+
No behaviour changes for a block that carries no `dataSource`: the binding-free
|
|
754
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
755
|
+
|
|
756
|
+
- 022002a: `PageComponentSchema.dataSource` now reaches the remaining object-bound public
|
|
757
|
+
blocks: `object-gantt` / `object-timeline` / `object-map` / `object-pivot` /
|
|
758
|
+
`object-master-detail-form` / `embeddable-form` / `record:line_items`
|
|
759
|
+
(objectstack#7121).
|
|
760
|
+
|
|
761
|
+
objectstack#6953 wired the spec's per-element data binding
|
|
762
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to the eight blocks it
|
|
763
|
+
named and left the same declaration inert on these seven. Each gates its fetch on
|
|
764
|
+
its own object key and nothing mapped `dataSource.object` onto it, so a page
|
|
765
|
+
written the way the spec documents rendered an empty gantt / an empty timeline
|
|
766
|
+
rail / a map with no markers / an empty cross-tab / a field-less form — with no
|
|
767
|
+
request and no diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
768
|
+
objectstack#4413 shape.
|
|
769
|
+
|
|
770
|
+
Composition follows objectstack#5576's landed semantics unchanged, through the
|
|
771
|
+
shared `ElementDataSourceGate` (no change to it or to the resolution layer): a
|
|
772
|
+
named saved view supplies the baseline, a key written on the component itself
|
|
773
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
774
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
775
|
+
a `view` name that does not resolve renders a configuration error on every one of
|
|
776
|
+
these blocks instead of degrading to the object's full scope.
|
|
777
|
+
|
|
778
|
+
Each block maps **only** the keys it genuinely reads, which for this batch means
|
|
779
|
+
several keys stay deliberately unmapped rather than being parked somewhere
|
|
780
|
+
plausible:
|
|
781
|
+
|
|
782
|
+
- `object-gantt` and `object-map` take `object` / `filter` / `sort`; neither has a
|
|
783
|
+
row cap or a field-list read site.
|
|
784
|
+
- `object-pivot` takes `object` / `filter`; a cross-tab orders itself by its own
|
|
785
|
+
row/column grouping and cannot be computed over a truncated page.
|
|
786
|
+
- `object-timeline` takes `object` only — its fetch is
|
|
787
|
+
`find(objectName, { options: { $top: 100 } })`, with no filter/sort read site
|
|
788
|
+
at all, so a named view is error-checked and then contributes nothing.
|
|
789
|
+
- `embeddable-form` and `object-master-detail-form` take `object` only (the
|
|
790
|
+
parent object, in the master-detail case); a form that writes one record has no
|
|
791
|
+
collection query for `filter` / `sort` / `limit` to narrow.
|
|
792
|
+
- `record:line_items` takes `object` onto **`childObject`** — the collection it
|
|
793
|
+
actually lists — and nothing else: its query is the parent FK plus a fixed
|
|
794
|
+
`$top: 500`, and its `columns` are editable `GridColumn` objects rather than a
|
|
795
|
+
field-name projection a view could supply.
|
|
796
|
+
|
|
797
|
+
The per-block coverage table, including every residual gap named above, is in
|
|
798
|
+
`content/docs/guide/data-source.md`.
|
|
799
|
+
|
|
800
|
+
No behaviour change for a block that carries no `dataSource`: the binding-free
|
|
801
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
802
|
+
|
|
803
|
+
- 02eb444: Show the `compareTo` comparison in a dataset pivot cross-tab instead of dropping it
|
|
804
|
+
|
|
805
|
+
A dataset widget with `type: 'pivot'` and two or more `dimensions` renders a true cross-tab, and that branch was the one render path the `compareTo` work left out (objectui#3614, following objectui#3337 / PR #3612). It laid out its columns as `bucket × measure` and never admitted the `<measure>__compare` columns the executor returns — so a pivot with a bounded date window and a `compareTo` ran a correct comparison query, received correct comparison data, and displayed none of it: headers, cells and all three subtotals were silent.
|
|
806
|
+
|
|
807
|
+
The comparison is now **stacked inside the cell** — current value on top, comparison value and its delta percentage beneath in smaller type:
|
|
808
|
+
|
|
809
|
+
- The pivot's column structure is unchanged. Giving the comparison a column of its own would turn `bucket × measure` into `bucket × measure × window`, doubling the width and adding a third header level on the widget family whose width is already the scarce resource.
|
|
810
|
+
- **Row, column and grand subtotals stack it the same way.** A Total that alone showed no comparison would read as "this row has none", which is a different and false statement.
|
|
811
|
+
- One caption names the comparison window ("vs last year") for the whole table, from the same `dashboard.trend.*` vocabulary the KPI and flat-table paths use, and the delta comes from the same helper — so a KPI and a cross-tab cell comparing the same two windows agree on sign and rounding.
|
|
812
|
+
- **CSV export stays data-shaped.** The cross-tab now exports a flat `<measure>__compare` column per compared measure, with bare numbers in the cells: a spreadsheet can compute on the export, and no stacked display string ("$120 $100 20%") ever reaches it.
|
|
813
|
+
|
|
814
|
+
Presence is detected from the returned data, as on every other path, so there is no new option to set — and a pivot the executor sent no comparison for renders exactly as it did before.
|
|
815
|
+
|
|
816
|
+
- c1e1e6b: Studio's widget config panel no longer authors the retired `actionUrl` widget key
|
|
817
|
+
|
|
818
|
+
`actionUrl` / `actionType` / `actionIcon` were retired at the WIDGET level in
|
|
819
|
+
`@objectstack/spec` 17.0.0-rc.3 (objectstack#5010, ADR-0049 D2). They are
|
|
820
|
+
`retiredKey` tombstones: `DashboardWidgetSchema` types them `never` and refuses
|
|
821
|
+
any value, so authoring one is a tsc error and a parse error. Two producers in
|
|
822
|
+
`plugin-dashboard` were still emitting the widget-level key anyway
|
|
823
|
+
(objectstack#7129):
|
|
824
|
+
|
|
825
|
+
- `WidgetConfigPanel` offered a Behavior-group field labelled "Click-through
|
|
826
|
+
URL", bound to `actionUrl`. That control was inert twice over: no dashboard
|
|
827
|
+
widget renderer has ever read `widget.actionUrl`, so a URL typed there never
|
|
828
|
+
navigated anywhere, and the value it wrote was refused by the spec.
|
|
829
|
+
- `DashboardWithConfig` seeded `actionUrl: widget.actionUrl ?? ''` into every
|
|
830
|
+
widget config handed to the panel. Because the ADR-0021 save scrub only knew
|
|
831
|
+
the dataset-shape keys, that seed rode through to `onWidgetSave` on EVERY
|
|
832
|
+
save — so a Studio author who merely renamed a widget still persisted
|
|
833
|
+
`actionUrl: ''` into stored metadata, a key the spec then refuses. This is
|
|
834
|
+
the wider half of the defect: it did not require anyone to use the field.
|
|
835
|
+
|
|
836
|
+
The Behavior group and the seed are both gone, and `sanitizeDraftForType` now
|
|
837
|
+
scrubs all three keys as a second line of defence, for stored widgets that
|
|
838
|
+
already carry them and for hosts that drive `WidgetConfigPanel` directly.
|
|
839
|
+
|
|
840
|
+
Behaviour change surface: the widget config panel loses its Behavior section
|
|
841
|
+
(that section contained only this one field). Nothing that rendered before stops
|
|
842
|
+
rendering — the field had no consumer. `header.actions[]` keeps its own,
|
|
843
|
+
unrelated and still-live `actionUrl`; only the widget-level key is a tombstone.
|
|
844
|
+
|
|
845
|
+
Also corrects the `DashboardWidgetSchema` docblock in `@object-ui/types`, which
|
|
846
|
+
listed the three retired keys among those that "flow in from the spec" next to
|
|
847
|
+
live keys like `colorVariant`. They do flow in — as `?: never`. The docblock now
|
|
848
|
+
says so, and notes that while authoring one is a tsc error, _reading_ one still
|
|
849
|
+
type-checks (`never | undefined`), which is exactly how these producers survived
|
|
850
|
+
the 2026-08-04 sweep that removed the renderer-side reads.
|
|
851
|
+
|
|
852
|
+
- Updated dependencies [794c497]
|
|
853
|
+
- Updated dependencies [993336f]
|
|
854
|
+
- Updated dependencies [f0a625a]
|
|
855
|
+
- Updated dependencies [b5980f4]
|
|
856
|
+
- Updated dependencies [8aad9fd]
|
|
857
|
+
- Updated dependencies [6719877]
|
|
858
|
+
- Updated dependencies [56ff091]
|
|
859
|
+
- Updated dependencies [0186cdc]
|
|
860
|
+
- Updated dependencies [7864f03]
|
|
861
|
+
- Updated dependencies [ea41a59]
|
|
862
|
+
- Updated dependencies [0cbdca8]
|
|
863
|
+
- Updated dependencies [d229dfa]
|
|
864
|
+
- Updated dependencies [ecae400]
|
|
865
|
+
- Updated dependencies [4bc6c23]
|
|
866
|
+
- Updated dependencies [d3e738a]
|
|
867
|
+
- Updated dependencies [c3b01a7]
|
|
868
|
+
- Updated dependencies [f5f8744]
|
|
869
|
+
- Updated dependencies [7ed3360]
|
|
870
|
+
- Updated dependencies [69becd2]
|
|
871
|
+
- Updated dependencies [5e52495]
|
|
872
|
+
- Updated dependencies [0fa5e4d]
|
|
873
|
+
- Updated dependencies [b750823]
|
|
874
|
+
- Updated dependencies [5bfaabd]
|
|
875
|
+
- Updated dependencies [e06810e]
|
|
876
|
+
- Updated dependencies [ab3ad4f]
|
|
877
|
+
- Updated dependencies [65bb513]
|
|
878
|
+
- Updated dependencies [c97a45e]
|
|
879
|
+
- Updated dependencies [b19162d]
|
|
880
|
+
- Updated dependencies [c2fd122]
|
|
881
|
+
- Updated dependencies [1bd6faa]
|
|
882
|
+
- Updated dependencies [ac2139c]
|
|
883
|
+
- Updated dependencies [b14ab3a]
|
|
884
|
+
- Updated dependencies [e24d767]
|
|
885
|
+
- Updated dependencies [8c60819]
|
|
886
|
+
- Updated dependencies [aca561a]
|
|
887
|
+
- Updated dependencies [e64a52e]
|
|
888
|
+
- Updated dependencies [844d17f]
|
|
889
|
+
- Updated dependencies [d8a0be4]
|
|
890
|
+
- Updated dependencies [48132f7]
|
|
891
|
+
- Updated dependencies [4dcd52a]
|
|
892
|
+
- Updated dependencies [42ae5c6]
|
|
893
|
+
- Updated dependencies [0ef9dfd]
|
|
894
|
+
- Updated dependencies [f4b97c8]
|
|
895
|
+
- Updated dependencies [1d723e3]
|
|
896
|
+
- Updated dependencies [0109f54]
|
|
897
|
+
- Updated dependencies [7e5bb5d]
|
|
898
|
+
- Updated dependencies [fbc23e0]
|
|
899
|
+
- Updated dependencies [6d762da]
|
|
900
|
+
- Updated dependencies [e6fdbdc]
|
|
901
|
+
- Updated dependencies [54233b1]
|
|
902
|
+
- Updated dependencies [c2ecbae]
|
|
903
|
+
- Updated dependencies [f9faa7d]
|
|
904
|
+
- Updated dependencies [97b63d7]
|
|
905
|
+
- Updated dependencies [6bb454a]
|
|
906
|
+
- Updated dependencies [11c1e71]
|
|
907
|
+
- Updated dependencies [523be48]
|
|
908
|
+
- Updated dependencies [7e2b7e9]
|
|
909
|
+
- Updated dependencies [33526fd]
|
|
910
|
+
- Updated dependencies [32413ec]
|
|
911
|
+
- Updated dependencies [c1e1e6b]
|
|
912
|
+
- @object-ui/components@17.4.0
|
|
913
|
+
- @object-ui/react@17.4.0
|
|
914
|
+
- @object-ui/core@17.4.0
|
|
915
|
+
- @object-ui/fields@17.4.0
|
|
916
|
+
- @object-ui/i18n@17.4.0
|
|
917
|
+
- @object-ui/types@17.4.0
|
|
918
|
+
|
|
3
919
|
## 17.3.0
|
|
4
920
|
|
|
5
921
|
### Patch Changes
|
|
@@ -1031,6 +1947,7 @@
|
|
|
1031
1947
|
project `health`) painted its categories from the generic `--chart-1..5`
|
|
1032
1948
|
palette — the same gap the chart view (`object-chart`) had before #1932. It now
|
|
1033
1949
|
resolves the dimension field's option colors (using the dataset's base `object`
|
|
1950
|
+
|
|
1034
1951
|
- dimension→field map the query already returns) and threads them to the
|
|
1035
1952
|
renderer as a per-category `categoryColors` map, so health green/red/yellow
|
|
1036
1953
|
paints semantically.
|
|
@@ -1282,6 +2199,7 @@
|
|
|
1282
2199
|
- 991b62d: Add `compareTo` field to dashboard widgets for period-over-period
|
|
1283
2200
|
comparison. Supports `'previousPeriod'`, `'previousYear'`, and
|
|
1284
2201
|
`{ offset: '7d' | '4w' | '1M' | '1y' }`.
|
|
2202
|
+
|
|
1285
2203
|
- **Metric / gauge widgets** now compute a delta percentage when `compareTo`
|
|
1286
2204
|
is set and surface it as a derived `trend` (auto-labelled via
|
|
1287
2205
|
`dashboard.trend.vsLast*` i18n keys sniffed from the filter macros).
|
|
@@ -1446,6 +2364,7 @@
|
|
|
1446
2364
|
platform primitive for "no records / no data" states. Two new props
|
|
1447
2365
|
keep it flexible enough to absorb the hand-rolled variants that lived
|
|
1448
2366
|
in `plugin-list`, `plugin-kanban`, and `plugin-dashboard`:
|
|
2367
|
+
|
|
1449
2368
|
- `showIcon?: boolean` — drops the icon container entirely. Used by the
|
|
1450
2369
|
kanban board-level empty banner, which is a status banner rather than
|
|
1451
2370
|
a true empty-state.
|
|
@@ -1454,6 +2373,7 @@
|
|
|
1454
2373
|
empty state, which uses a large standalone glyph).
|
|
1455
2374
|
|
|
1456
2375
|
Adopters:
|
|
2376
|
+
|
|
1457
2377
|
- `plugin-list` (`ListView` grid empty-state) — preserves the existing
|
|
1458
2378
|
large icon, title, message, add-record button and `data-testid`s,
|
|
1459
2379
|
but delegates the structural markup to `DataEmptyState`.
|
|
@@ -1476,6 +2396,7 @@
|
|
|
1476
2396
|
`defaultCurrency` is set on the field/column, formatting is unchanged.
|
|
1477
2397
|
|
|
1478
2398
|
Fixed call sites:
|
|
2399
|
+
|
|
1479
2400
|
- `@object-ui/fields`: `formatCurrency`, `formatCompactCurrency`, and
|
|
1480
2401
|
`CurrencyCellRenderer` no longer default-param `'USD'`.
|
|
1481
2402
|
- `@object-ui/i18n`: `formatCurrency()` falls back to `formatNumber`
|
|
@@ -1857,6 +2778,7 @@
|
|
|
1857
2778
|
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.
|
|
1858
2779
|
|
|
1859
2780
|
Specifically fixes:
|
|
2781
|
+
|
|
1860
2782
|
- `@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.
|
|
1861
2783
|
- `@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.
|
|
1862
2784
|
- All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.
|