From 6e9abeadacf0b5357a2041d0fd9947ae7213bf0d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 10:17:00 +0000 Subject: [PATCH 1/3] fix(plugin-dashboard): the editable grid's object-bound table widget fetches its rows (objectui#10528) DashboardGridLayout mapped a table-family widget whose data is `{ provider: 'object', object }` to a static `data-table` node with `data: []` and an `objectName` that `data-table` never reads, so the tile drew an empty table and issued no query. DashboardRenderer maps the same stored widget to the self-fetching `object-data-table`. The grid's table arm now emits that node with the renderer's props: the provider filter, the declared searchable/pagination (never on a list), and default-on drill-to-record. Drag stays gated to the `.drag-handle` element, and the grid has no widget selection, so the row drill does not fight the editor. A parity pin holds the two copies equal. The static branches and the provider-object pivot arm are unchanged. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude --- .changeset/10528-grid-object-table-fetch.md | 26 ++ .../src/DashboardGridLayout.tsx | 23 +- ...dLayout.objectProviderFetch-10528.test.tsx | 239 ++++++++++++++++++ 3 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 .changeset/10528-grid-object-table-fetch.md create mode 100644 packages/plugin-dashboard/src/__tests__/DashboardGridLayout.objectProviderFetch-10528.test.tsx diff --git a/.changeset/10528-grid-object-table-fetch.md b/.changeset/10528-grid-object-table-fetch.md new file mode 100644 index 0000000000..3e2a0a41f9 --- /dev/null +++ b/.changeset/10528-grid-object-table-fetch.md @@ -0,0 +1,26 @@ +--- +'@object-ui/plugin-dashboard': patch +--- + +Fix: on the editable dashboard grid (`dashboard-grid`, `DashboardGridLayout`), a +table or list widget whose data is `{ provider: 'object', object }` now fetches +its rows and draws them (objectui#10528). + +Before this change the grid turned that widget into a static `data-table` node +with `data: []`. That node never reads `objectName`, so the tile showed an empty +table and sent no query. The read-only `DashboardRenderer` drew the same stored +widget correctly. + +The grid now builds the same node `DashboardRenderer` builds for that widget: an +`object-data-table`, which loads the object's rows through the data source. The +props match too: + +- the provider's `filter` (or the widget's `filter`) reaches the query; +- the widget's `searchable` and `pagination` are honoured, and a `list` never + gets them; +- a row click opens the record in a drawer by default, as on the read-only + dashboard. `options.drillDown` still overrides this. In edit mode, dragging + still starts only from the drag handle. + +Tables bound to inline rows are unchanged. A `pivot` widget with a +`provider: 'object'` config is also unchanged in this release. diff --git a/packages/plugin-dashboard/src/DashboardGridLayout.tsx b/packages/plugin-dashboard/src/DashboardGridLayout.tsx index a49bed36b6..5594512c6a 100644 --- a/packages/plugin-dashboard/src/DashboardGridLayout.tsx +++ b/packages/plugin-dashboard/src/DashboardGridLayout.tsx @@ -376,16 +376,29 @@ export const DashboardGridLayout: React.FC = ({ if (dispatch.family === 'table') { const widgetData = (widget as any).data || options.data; - // provider: 'object' — pass through object config for async data loading + // provider: 'object' — ObjectDataTable fetches the rows (objectui#10528). + // This arm used to emit a STATIC `data-table` with `data: []` and an + // `objectName` that `data-table` never reads, so the tile drew an empty + // table and issued no query, while `DashboardRenderer` fetched and drew + // the same stored widget. It now emits the node that renderer's table arm + // emits, prop for prop: the self-fetching `object-data-table`, the + // provider's `filter`, the declared `searchable` / `pagination` (never on + // a `list`), and default-on drill-to-record. The drill cannot fight the + // editor: dragging starts only from the `.drag-handle` element (see + // `dragConfig` below), and this grid has no widget selection. The two + // copies are held equal by + // `DashboardGridLayout.objectProviderFetch-10528.test.tsx`. if (isObjectProvider(widgetData)) { + const isList = widgetType === 'list'; const { data: _data, ...restOptions } = options; return { - type: 'data-table', + type: 'object-data-table', ...restOptions, objectName: widgetData.object, - data: [], - searchable: false, - pagination: false, + filter: widgetData.filter || widget.filter, + searchable: isList ? false : (widget.searchable ?? false), + pagination: isList ? false : (widget.pagination ?? false), + drillDown: options.drillDown ?? { enabled: true, mode: 'record' as const }, className: "border-0" }; } diff --git a/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.objectProviderFetch-10528.test.tsx b/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.objectProviderFetch-10528.test.tsx new file mode 100644 index 0000000000..d97afba9d7 --- /dev/null +++ b/packages/plugin-dashboard/src/__tests__/DashboardGridLayout.objectProviderFetch-10528.test.tsx @@ -0,0 +1,239 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * objectui#10528 — on the editable `dashboard-grid`, a `provider: 'object'` + * table widget fetches its rows and draws them. + * + * ## The defect + * + * `DashboardGridLayout` mapped a table-family widget whose data is + * `{ provider: 'object', object }` to a STATIC `data-table` node with + * `data: []` and `objectName` beside it. `data-table` reads no `objectName`, so + * the tile drew an empty table and issued no query. The read dashboard + * (`DashboardRenderer`) maps the same stored widget to `object-data-table`, + * the self-fetching node, so the two dashboard surfaces disagreed on the same + * metadata. + * + * ## The fix, and what this file pins + * + * The grid's table arm now emits the same `object-data-table` node, with the + * same props, that `DashboardRenderer`'s table arm emits. Three things are + * pinned: + * + * 1. The grid composes `object-data-table` (bound through `objectName`, with + * no retired `dataProvider`, objectui#7353). The adapter's `find` is called + * for that object, and the rows it returns are on screen. + * 2. Parity: for the same stored widget, the node the grid composes EQUALS the + * node `DashboardRenderer` composes. `DashboardRenderer.tsx` is not edited + * here, so this equality is what stops the two copies from drifting. + * 3. Lit controls: a static-data table and a static-data pivot on the grid + * still draw their authored rows (objectui#4618's `Array.isArray` arm). + * + * ⚠️ Not pinned here: the grid's `provider: 'object'` PIVOT arm. It is left + * unchanged. The read dashboard answers a non-dataset pivot with the retired + * placeholder (ADR-0021: a pivot is dataset-bound), so the grid's pivot arm + * has no node on the read surface to copy. The pivot half is an open question + * on the card. + * + * ## Why a listener, not a recorder + * + * The node is recorded and then rendered by the REAL `SchemaRenderer`, so the + * widget mounts, fetches and draws as it does in production. That is the same + * shape `widgetDataProviderRetired-7353.test.tsx` uses. + */ + +import * as React from 'react'; +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { render, screen, waitFor, cleanup, within } from '@testing-library/react'; + +/** Every node handed to `SchemaRenderer`, in arrival order. */ +const received = vi.hoisted(() => [] as Array>); + +vi.mock('@object-ui/react', async () => { + const actual: any = await vi.importActual('@object-ui/react'); + const RealSchemaRenderer = actual.SchemaRenderer; + return { + ...actual, + // A listener, not a stub: the node is recorded and then rendered by the + // REAL renderer. Everything else is the real export by identity. + SchemaRenderer: (props: any) => { + if (props?.schema && typeof props.schema === 'object') received.push(props.schema); + return ; + }, + }; +}); + +import { SchemaRendererProvider } from '@object-ui/react'; +import type { DashboardComponentSchema } from '@object-ui/types'; +// Side-effect imports at MODULE scope (never inside a case or a hook), per +// AGENTS.md's flaky-test rule: `@object-ui/components` registers `data-table`, +// and the package barrel registers `object-data-table` / `pivot`. +import '@object-ui/components'; +import { DashboardRenderer, DashboardGridLayout } from '../index'; + +afterEach(() => { + cleanup(); + received.length = 0; +}); + +const ROWS = [ + { id: 'a1', name: 'Acme', region: 'EMEA', amount: 3 }, + { id: 'a2', name: 'Globex', region: 'APAC', amount: 5 }, +]; + +/** A real adapter surface: `find` records every call and answers with ROWS. */ +function makeAdapter() { + return { + find: vi.fn(async (_objectName: string, _params?: unknown) => ({ data: ROWS })), + getObjectSchema: vi.fn(async (_objectName: string) => ({ + name: 'account', + fields: { name: { type: 'text', label: 'Name' }, region: { type: 'text' }, amount: { type: 'number' } }, + })), + }; +} + +const dash = (widgets: Record[]): DashboardComponentSchema => + ({ type: 'dashboard', widgets }) as unknown as DashboardComponentSchema; + +const nodesOfType = (type: string) => received.filter((n) => n.type === type); + +function renderGrid(widget: Record, adapter = makeAdapter()) { + render( + + + , + ); + return adapter; +} + +describe('objectui#10528 — grid: a provider-object table widget fetches and draws its rows', () => { + it('composes object-data-table, queries the object, and draws the returned rows', async () => { + const adapter = renderGrid({ + id: 'accounts', + type: 'table', + title: 'Accounts', + options: { data: { provider: 'object', object: 'account' } }, + }); + + // The rows the adapter returned are on screen. + expect(await screen.findByText('Acme')).toBeTruthy(); + expect(screen.getByText('Globex')).toBeTruthy(); + + // The query was issued, and only for the bound object. + expect(adapter.find).toHaveBeenCalled(); + expect(adapter.find.mock.calls.every(([objectName]) => objectName === 'account')).toBe(true); + + // The node the grid composed is the self-fetching one, bound through + // objectName, without the key objectui#7353 retired. + const widgetNodes = nodesOfType('object-data-table'); + expect(widgetNodes.length).toBeGreaterThan(0); + for (const node of widgetNodes) { + expect(node.objectName).toBe('account'); + expect(Object.keys(node)).not.toContain('dataProvider'); + } + }); + + it("forwards the provider's filter to the query", async () => { + const filter = [['region', '=', 'EMEA']]; + const adapter = renderGrid({ + id: 'emea', + type: 'table', + title: 'EMEA accounts', + options: { data: { provider: 'object', object: 'account', filter } }, + }); + + await screen.findByText('Acme'); + const params = adapter.find.mock.calls.map(([, p]) => p as { $filter?: unknown }); + expect(params.length).toBeGreaterThan(0); + expect(params.every((p) => JSON.stringify(p.$filter) === JSON.stringify(filter))).toBe(true); + }); +}); + +describe('objectui#10528 — grid and renderer compose the same object-data-table node', () => { + it.each([ + ['a plain table', { type: 'table', options: { data: { provider: 'object', object: 'account' } } }], + [ + 'a table with a provider filter, search and pagination', + { + type: 'table', + searchable: true, + pagination: true, + options: { columns: ['name', 'region'], data: { provider: 'object', object: 'account', filter: [['region', '=', 'EMEA']] } }, + }, + ], + ['a list, which never carries search or pagination chrome', { type: 'list', searchable: true, pagination: true, options: { data: { provider: 'object', object: 'account' } } }], + [ + 'a table with an authored drillDown', + { type: 'table', options: { drillDown: { enabled: false }, data: { provider: 'object', object: 'account' } } }, + ], + ])('%s', async (_label, shape) => { + const widget = { id: 'w1', title: 'Accounts', ...shape }; + + const gridAdapter = renderGrid(widget); + await waitFor(() => expect(nodesOfType('object-data-table').length).toBeGreaterThan(0)); + const gridNode = nodesOfType('object-data-table')[0]; + cleanup(); + received.length = 0; + + const rendererAdapter = makeAdapter(); + render( + + + , + ); + await waitFor(() => expect(nodesOfType('object-data-table').length).toBeGreaterThan(0)); + const rendererNode = nodesOfType('object-data-table')[0]; + + expect(gridNode).toEqual(rendererNode); + // Non-vacuity: both surfaces really bound the object. + expect(gridNode.objectName).toBe('account'); + expect(gridAdapter.find).toHaveBeenCalled(); + }); +}); + +describe('objectui#10528 — lit controls: static-data widgets on the grid draw their authored rows', () => { + it('a static-data table draws its authored rows and issues no query', async () => { + const adapter = renderGrid({ + id: 'static-table', + type: 'table', + title: 'Static', + options: { data: [{ name: 'Initech', amount: 7 }, { name: 'Umbrella', amount: 9 }] }, + }); + + expect(await screen.findByText('Initech')).toBeTruthy(); + expect(screen.getByText('Umbrella')).toBeTruthy(); + expect(nodesOfType('data-table').length).toBeGreaterThan(0); + expect(nodesOfType('object-data-table')).toHaveLength(0); + expect(adapter.find).not.toHaveBeenCalled(); + }); + + it('a static-data pivot draws its authored cross-tab and issues no query', async () => { + const adapter = renderGrid({ + id: 'static-pivot', + type: 'pivot', + title: 'Static pivot', + options: { + rowField: 'region', + columnField: 'stage', + valueField: 'amount', + aggregation: 'sum', + data: [ + { region: 'North', stage: 'won', amount: 4 }, + { region: 'South', stage: 'lost', amount: 6 }, + ], + }, + }); + + const grid = await screen.findByTestId('grid-layout'); + expect(await within(grid).findByText('North')).toBeTruthy(); + expect(within(grid).getByText('South')).toBeTruthy(); + expect(nodesOfType('pivot').length).toBeGreaterThan(0); + expect(adapter.find).not.toHaveBeenCalled(); + }); +}); From 7a4f6978c4af0bf145c0b6ea9f4c1fe76c39b506 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 11:44:52 +0000 Subject: [PATCH 2/3] fix(plugin-dashboard): the editable grid retires a provider-object pivot like the read dashboard (objectui#10528) Round 2 of the card, amended claim 5831757648 (option B). The grid's pivot arm turned a `provider: 'object'` pivot into a static `pivot` node with `data: []` and an `objectName` PivotTable never reads, so it drew an empty cross-tab and sent no query. DashboardRenderer answers every non-dataset pivot with LEGACY_RETIRED_WIDGET_SCHEMA, since ADR-0021 puts a cross-tab on the dataset layer only. The grid's provider-object pivot branch now returns that same shared object. The static-data pivot branch is unchanged. DashboardRenderer's pivot-arm comment (comment only) now says the grid still draws static-data pivots and retires the provider-object shape. The 7353 pin's grid-pivot row, which pinned the removed branch, is replaced by a row pinning the placeholder. The 10528 pin gains a placeholder case and a same-object parity case against the renderer. The changeset covers both halves and stays patch. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude --- .changeset/10528-grid-object-table-fetch.md | 29 ++++---- .../src/DashboardGridLayout.tsx | 24 ++++--- .../src/DashboardRenderer.tsx | 12 ++-- ...dLayout.objectProviderFetch-10528.test.tsx | 72 +++++++++++++++---- .../widgetDataProviderRetired-7353.test.tsx | 25 ++++++- 5 files changed, 120 insertions(+), 42 deletions(-) diff --git a/.changeset/10528-grid-object-table-fetch.md b/.changeset/10528-grid-object-table-fetch.md index 3e2a0a41f9..af23bd1f9a 100644 --- a/.changeset/10528-grid-object-table-fetch.md +++ b/.changeset/10528-grid-object-table-fetch.md @@ -2,18 +2,16 @@ '@object-ui/plugin-dashboard': patch --- -Fix: on the editable dashboard grid (`dashboard-grid`, `DashboardGridLayout`), a -table or list widget whose data is `{ provider: 'object', object }` now fetches -its rows and draws them (objectui#10528). +Fix: the editable dashboard grid (`dashboard-grid`, `DashboardGridLayout`) now +treats widgets whose data is `{ provider: 'object', object }` the way the +read-only `DashboardRenderer` already does (objectui#10528). Before this change +the grid turned a table, list or pivot widget with that config into a static +node with `data: []`. The tile showed an empty table or cross-tab and sent no +query. -Before this change the grid turned that widget into a static `data-table` node -with `data: []`. That node never reads `objectName`, so the tile showed an empty -table and sent no query. The read-only `DashboardRenderer` drew the same stored -widget correctly. - -The grid now builds the same node `DashboardRenderer` builds for that widget: an -`object-data-table`, which loads the object's rows through the data source. The -props match too: +**Table and list widgets now fetch their rows.** The grid builds the same node +`DashboardRenderer` builds for the widget: an `object-data-table`, which loads +the object's rows through the data source. The props match too: - the provider's `filter` (or the widget's `filter`) reaches the query; - the widget's `searchable` and `pagination` are honoured, and a `list` never @@ -22,5 +20,10 @@ props match too: dashboard. `options.drillDown` still overrides this. In edit mode, dragging still starts only from the drag handle. -Tables bound to inline rows are unchanged. A `pivot` widget with a -`provider: 'object'` config is also unchanged in this release. +**Pivot widgets with that config now show the retired-widget placeholder.** +It is the same placeholder `DashboardRenderer` already shows for them ("This +widget uses a retired data format. Edit it to bind a dataset."). Under ADR-0021 +a pivot is bound to a dataset, and a dataset-bound pivot still renders as +before. + +Widgets bound to inline rows are unchanged, including static-data pivots. diff --git a/packages/plugin-dashboard/src/DashboardGridLayout.tsx b/packages/plugin-dashboard/src/DashboardGridLayout.tsx index 5594512c6a..5dc89294d7 100644 --- a/packages/plugin-dashboard/src/DashboardGridLayout.tsx +++ b/packages/plugin-dashboard/src/DashboardGridLayout.tsx @@ -427,16 +427,20 @@ export const DashboardGridLayout: React.FC = ({ if (dispatch.family === 'pivot') { const widgetData = (widget as any).data || options.data; - // provider: 'object' — pass through object config for async data loading - if (isObjectProvider(widgetData)) { - const { data: _data, ...restOptions } = options; - return { - type: 'pivot', - ...restOptions, - objectName: widgetData.object, - data: [], - }; - } + // provider: 'object' — RETIRED, with the same placeholder object + // `DashboardRenderer`'s pivot arm returns (objectui#10528). A pivot is a + // cross-tab, and ADR-0021 puts cross-tabs on the dataset layer only: the + // shared dispatch types this family "dataset-bound only; a non-dataset + // pivot is stale metadata" (`widgetDispatch.ts`). This branch used to emit + // a static `pivot` node with `data: []` and an `objectName` that + // `PivotTable` never reads, so the tile drew an empty cross-tab and sent + // no query, while the read dashboard showed this placeholder for the same + // widget. Mapping it to the self-fetching `object-pivot` instead would + // have revived the removed inline analytics shape in the editor alone. + // A dataset-bound pivot still renders through `DatasetWidget` (the fork + // at the render site below), and the static-data pivot under this branch + // is unchanged. + if (isObjectProvider(widgetData)) return LEGACY_RETIRED_WIDGET_SCHEMA; return { type: 'pivot', diff --git a/packages/plugin-dashboard/src/DashboardRenderer.tsx b/packages/plugin-dashboard/src/DashboardRenderer.tsx index fb412049b6..6cfd60627e 100644 --- a/packages/plugin-dashboard/src/DashboardRenderer.tsx +++ b/packages/plugin-dashboard/src/DashboardRenderer.tsx @@ -826,11 +826,13 @@ const DashboardRendererInner = forwardRef { cleanup(); @@ -197,6 +200,49 @@ describe('objectui#10528 — grid and renderer compose the same object-data-tabl }); }); +describe('objectui#10528 — grid: a provider-object pivot widget shows the retired-widget placeholder', () => { + const pivotWidget = { + id: 'account-pivot', + type: 'pivot', + title: 'Accounts by region', + options: { + rowField: 'region', + columnField: 'stage', + valueField: 'amount', + aggregation: 'sum', + data: { provider: 'object', object: 'account' }, + }, + }; + + it('renders the placeholder, composes no pivot node, and issues no query', async () => { + const adapter = renderGrid(pivotWidget); + + expect(await screen.findByText(LEGACY_RETIRED_WIDGET_SCHEMA.content)).toBeTruthy(); + expect(received).toContain(LEGACY_RETIRED_WIDGET_SCHEMA); + expect(nodesOfType('pivot')).toHaveLength(0); + expect(nodesOfType('object-pivot')).toHaveLength(0); + expect(adapter.find).not.toHaveBeenCalled(); + }); + + it('is the same placeholder object DashboardRenderer composes for the same widget', async () => { + renderGrid(pivotWidget); + await waitFor(() => expect(received).toContain(LEGACY_RETIRED_WIDGET_SCHEMA)); + cleanup(); + received.length = 0; + + const rendererAdapter = makeAdapter(); + render( + + + , + ); + // `toContain` compares by identity: both surfaces hand SchemaRenderer the + // one shared declaration, never a restated copy of it. + await waitFor(() => expect(received).toContain(LEGACY_RETIRED_WIDGET_SCHEMA)); + expect(rendererAdapter.find).not.toHaveBeenCalled(); + }); +}); + describe('objectui#10528 — lit controls: static-data widgets on the grid draw their authored rows', () => { it('a static-data table draws its authored rows and issues no query', async () => { const adapter = renderGrid({ diff --git a/packages/plugin-dashboard/src/__tests__/widgetDataProviderRetired-7353.test.tsx b/packages/plugin-dashboard/src/__tests__/widgetDataProviderRetired-7353.test.tsx index aaadfb565a..95efacbf01 100644 --- a/packages/plugin-dashboard/src/__tests__/widgetDataProviderRetired-7353.test.tsx +++ b/packages/plugin-dashboard/src/__tests__/widgetDataProviderRetired-7353.test.tsx @@ -82,6 +82,7 @@ import type { DashboardComponentSchema } from '@object-ui/types'; // the package barrel registers `object-data-table` / `pivot`. import '@object-ui/components'; import { DashboardRenderer, DashboardGridLayout } from '../index'; +import { LEGACY_RETIRED_WIDGET_SCHEMA } from '../legacyRetiredWidget'; import type { ObjectPivotTableProps } from '../ObjectPivotTable'; import type { ObjectDataTableProps } from '../ObjectDataTable'; @@ -147,9 +148,13 @@ describe('objectui#7353 — DashboardRenderer: the object-data-table node carrie }); describe('objectui#7353 — DashboardGridLayout: the table and pivot nodes carry no dataProvider', () => { + // The pivot row used to sit in this table as `['pivot', 'pivot', …]`, + // pinning a `pivot` node with `objectName`. objectui#10528 (amended claim + // 5831757648, option B) retired that branch: the grid's provider-object + // pivot now answers with the shared retired-widget placeholder, as + // `DashboardRenderer` does, so its row is the separate case below. it.each([ ['table', 'data-table', {}], - ['pivot', 'pivot', { rowField: 'region', valueField: 'amount' }], ])('a provider-object %s widget becomes a %s node with objectName and no dataProvider', async (widgetType, nodeType, extra) => { const adapter = makeAdapter(); render( @@ -167,6 +172,24 @@ describe('objectui#7353 — DashboardGridLayout: the table and pivot nodes carry expect(Object.keys(node)).not.toContain('dataProvider'); } }); + + it('a provider-object pivot widget becomes the retired-widget placeholder, which carries no dataProvider (objectui#10528)', async () => { + const adapter = makeAdapter(); + render( + + + , + ); + + await waitFor(() => expect(received).toContain(LEGACY_RETIRED_WIDGET_SCHEMA)); + // The shared object itself, not a copy — and it has no `dataProvider`. + expect(Object.keys(LEGACY_RETIRED_WIDGET_SCHEMA)).not.toContain('dataProvider'); + // The retired branch's node is gone: no `pivot` node, and no query. + expect(nodesOfType('pivot')).toHaveLength(0); + expect(adapter.find).not.toHaveBeenCalled(); + }); }); /* From 3e3c9aeedaa2231afe3e6b700669a1a8b491e5c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 12:32:47 +0000 Subject: [PATCH 3/3] docs(changeset): the pending 7353 note reads true for the grid after objectui#10528 The pending `.changeset/7353-retire-widget-dataprovider.md` said the grid's provider-object `data-table` and `pivot` nodes still write `objectName`. After objectui#10528 the grid builds the self-fetching `object-data-table` for such a table widget and shows the retired-widget placeholder for such a pivot widget, which writes no `objectName`. Only those two sentences of that bullet change; the front matter and every other sentence are byte-identical. Prose only, authorized by the seat for this card's ACCEPT. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude --- .changeset/7353-retire-widget-dataprovider.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.changeset/7353-retire-widget-dataprovider.md b/.changeset/7353-retire-widget-dataprovider.md index cf6cc63a0d..c1c6f1fbaf 100644 --- a/.changeset/7353-retire-widget-dataprovider.md +++ b/.changeset/7353-retire-widget-dataprovider.md @@ -15,9 +15,12 @@ as `dataProvider`, next to `objectName`. The widgets read only `objectName`, so version policy).** Compared with the released 17.6.0: - `DashboardRenderer` no longer writes `dataProvider` on the `object-data-table` - node, and `DashboardGridLayout` no longer writes it on the `data-table` and - `pivot` nodes. All three still write `objectName`, and the object-backed table - widget still fetches through it. + node, and `DashboardGridLayout` no longer writes it on any node it builds for a + `provider: 'object'` widget. Since objectui#10528 the grid builds that same + self-fetching `object-data-table` node for such a table widget, and shows the + retired-widget placeholder for such a pivot widget. The `object-data-table` + node still writes `objectName` on both surfaces, and the object-backed table + widget fetches through it. - In 17.6.0, the `schema` prop types of `ObjectPivotTable` and `ObjectDataTable` both declare `dataProvider?: { provider: string; object?: string }`. Both now declare it as a retirement tombstone (`dataProvider?: never`). TypeScript code