Skip to content

Commit 668dd59

Browse files
fix(plugin-dashboard): the editable grid's object-bound table fetches its rows, and its object-bound pivot shows the retired-widget placeholder (objectui#10528) (#10596)
Fixes #10528 Clause-②: no ## What changed The editable `dashboard-grid` (`DashboardGridLayout`) turned a table-family widget whose data is `{ provider: 'object', object }` into a static `data-table` node. That node had `data: []` and an `objectName` that `data-table` never reads. So the tile drew an empty table and sent no query. `DashboardRenderer` turns the same stored widget into `object-data-table`, which fetches its rows. The grid's table arm now emits the same node `DashboardRenderer`'s table arm emits, prop for prop: | Prop | Grid before | Grid now (same as the renderer) | |---|---|---| | `type` | `data-table` | `object-data-table` | | `data` | `[]` | not set (the node fetches) | | `filter` | not set | the provider's `filter`, else the widget's `filter` | | `searchable` / `pagination` | always `false` | `widget.searchable ?? false` / `widget.pagination ?? false`; always `false` on a `list` | | `drillDown` | not set | `options.drillDown ?? { enabled: true, mode: 'record' }` | | `objectName`, `className`, and the `...options` spread without `data` | unchanged | unchanged | Files changed, round 1 (the table half): - `packages/plugin-dashboard/src/DashboardGridLayout.tsx`: only the `provider: 'object'` branch of the table arm; - one new pin file; - one changeset. Rounds 2 and 3 (below) add the pivot half and two prose corrections: at the head the PR changes six files. `DashboardGridLayout.tsx`'s provider-object pivot branch, `DashboardRenderer.tsx`'s pivot-arm comment (comment only), the 7353 pin's grid-pivot row and the pending `7353` changeset's prose all move. These stay byte-unchanged: the static-data branches of the table and pivot arms. ## drillDown on the editable grid: kept on by default, as on the renderer I measured this with a one-off probe on the fixed tree, in "Edit Layout" mode. The probe was not committed. happy-dom gives the grid a zero width and a null `offsetParent`, and react-grid-layout's drag start returns early without an `offsetParent`. So the probe stubbed both. In the first two runs, without those stubs, the positive control did not fire either, so I discarded those readings. | Probe leg | Reading | |---|---| | drag handle rendered in edit mode | true | | row cell is inside `.drag-handle` | false | | press and move on the row cell starts a drag (`react-draggable-dragging`) | false | | press and move on the drag handle starts a drag (positive control) | true | | row click opens the record drawer (`record-detail-body`) | true | On the grid, a drag can start only from `dragConfig.handle: '.drag-handle'`. The grid also has no widget selection: there is no `onWidgetClick` or `selectedWidgetId`, unlike `DashboardRenderer`'s design mode. So a row click cannot start a drag or a selection, and the renderer's default stays as it is. There is no difference to pin, and the parity pin keeps the two nodes equal. ## Round 1: the provider-object pivot arm stopped at the decision I measured the dispatch's pivot hypothesis, and the pivot half stops here: - `object-pivot` (`ObjectPivotTable`) exists and is registered. It fetches through `objectName` and `filter`, then cross-tabs the rows client-side over `rowField` / `columnField` / `valueField` / `aggregation`. It reads no provider `aggregate`. - Mapping the grid's pivot arm to it would contradict ADR-0021 in the tree's own words: - `widgetDispatch.ts` documents the family as "Cross-tab — dataset-bound only; a non-dataset pivot is stale metadata." - `DashboardRenderer`'s pivot arm answers every non-dataset pivot with `LEGACY_RETIRED_WIDGET_SCHEMA`. - The spec's widget guidance set `LEGACY_WIDGET_ANALYTICS_KEYS` names `rowField` / `columnField` / `valueField` / `aggregation` as part of the removed pre-ADR-0021 inline analytics shape. Round 1 left that arm untouched and took the question to the card, analysed on the four axes. The seat decided it (option B) in the amended claim `5831757648`; round 2 below carries it out. ## Round 2 (claim `5831757648`, head `7a4f697`) The seat took option B for the pivot half (amended claim `5831757648`): the grid's `provider: 'object'` pivot arm is retired, the same answer the read dashboard gives (ADR-0021). - `DashboardGridLayout.tsx`: the pivot arm's provider-object branch returns `LEGACY_RETIRED_WIDGET_SCHEMA`, imported from `./legacyRetiredWidget` (the renderer's own source; no second copy). The static-data pivot branch is byte-unchanged. - `DashboardRenderer.tsx`: comment only (0 non-comment lines). The pivot-arm comment now says the grid still draws static-data pivots and answers the provider-object pivot with the same placeholder. - `widgetDataProviderRetired-7353.test.tsx`: the grid-pivot row that pinned the removed branch is replaced by a row pinning the placeholder (identity), with no `find` call, citing the claim. - New pins in `DashboardGridLayout.objectProviderFetch-10528.test.tsx`: an object-provider pivot on the grid renders the placeholder and calls no `find`; the grid and `DashboardRenderer` hand `SchemaRenderer` the same placeholder object for the same widget. The static-data table and pivot lit controls and all table-half pins stay green. - Reverse verification: with `DashboardGridLayout.tsx` at the round-2 base, `Tests 3 failed | 12 passed (15)` (exit 1; exactly the three pivot cases); at `7a4f697`, `Tests 15 passed (15)` (exit 0). - Producer census re-measured with a quote-agnostic pattern and positive controls: still zero provider-object pivot producers in either tree. - Gates on `7a4f697`: the plugin-dashboard suite plus 13 cross-package readers (143 files, 1712 tests, exit 0); `type-check` for `@object-ui/plugin-dashboard` (exit 0); eslint (0 errors, no new warnings); the control-byte, test-path, vi-mock, line-citation and changeset gates (exit 0 each); `check-governed-queue-guard` NOT GOVERNED. CI: 43 check-runs, 40 success, 3 skipped, 0 failed. - The changeset now covers both halves and stays `patch`. ## Pins `packages/plugin-dashboard/src/__tests__/DashboardGridLayout.objectProviderFetch-10528.test.tsx` held 8 tests in round 1; round 2 adds the two pivot cases (10 at the head): - **Grid, provider-object table.** The node is `object-data-table`, with `objectName: 'account'` and no `dataProvider`. The adapter's `find` is called, and only for `account`. The rows it returns (`Acme`, `Globex`) are on screen. - **Grid, filter.** The provider's `filter` reaches `find` as `$filter`. - **Parity, over 4 widgets.** For each widget, the node the grid composes `toEqual`s the node `DashboardRenderer` composes. The widgets: a plain table; a table with a provider filter, `searchable`, `pagination` and `columns`; a `list` with `searchable` and `pagination` authored; a table with an authored `drillDown`. - **Lit controls.** On the grid, a static-data table and a static-data pivot draw their authored rows, compose `data-table` / `pivot`, and never call `find`. Red, then green, run from the committed state. The script is trap-guarded. The restore is proven by the file's blob hash equalling `HEAD`'s and by an empty `git diff HEAD`. | Tree | Pin result | Exit | |---|---|---| | `DashboardGridLayout.tsx` at base `e5f4343` | 6 failed, 2 passed (the two lit controls). First failure: "Unable to find an element with the text: Acme" | 1 | | `HEAD` `6e9abea` | 8 passed | 0 | ## Gates, on `6fce3f1` (after merging `origin/main` at `caf0ed0`) - **Tests.** `pnpm exec vitest run --maxWorkers=2 packages/plugin-dashboard/`, plus the 9 test files outside the package that render or read the grid: 139 files (130 package files + 9), 1562 tests passed, exit 0. - **Type-check.** `pnpm turbo run type-check --filter @object-ui/plugin-dashboard --concurrency=2`: 14/14 tasks, exit 0. The package task was a cache miss and ran both `tsc` legs, and `tsconfig.test.json --listFilesOnly` lists the new pin. - **eslint** on the 2 changed files: 0 errors, exit 0. See Acceptance notes. - **Check gates**, each at exit 0 with its own verdict line clean: - `check:control-bytes`, `check:test-path-roots` - `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape` - `check:new-line-citations` (0 new; report-only) - `check:changeset-claims`, `check:pending-changeset-literals` - `check-changeset-presence`, `check-changeset-no-major` - **Governed surface.** `check-governed-queue-guard --test` on the 3 paths: NOT GOVERNED. ## Acceptance notes - **eslint narrowing.** - `--format json` counts 2 files linted. - The grid file has the same 7 warnings, by rule, on base and head. - The new pin adds 2 `no-explicit-any` warnings, in the listener mock copied from `widgetDataProviderRetired-7353.test.tsx`. This repo deliberately leaves `--max-warnings` unset. - Type-aware linting is not enabled, and no `eslint-rules/` rule reads other files. So this diff cannot change the lint verdict of any file it does not touch. - The repo-wide `pnpm lint` run is CI's. - **The 7353 pin's grid table row.** In `widgetDataProviderRetired-7353.test.tsx`, the grid table row still passes. It now reads the `data-table` node that `ObjectDataTable` renders onward, not a node the grid composes. The new pin checks that the grid's own `object-data-table` node carries no `dataProvider`. The row's name ("becomes a data-table node") now describes that onward node. That table row is left as it is; round 2 edits only the file's grid-pivot row, which the amended claim `5831757648` brought into the surface. - **The pending 7353 changeset.** Round 2 made its sentence about the grid's `data-table` / `pivot` nodes false (the grid now builds `object-data-table` for a provider-object table and the retired-widget placeholder for a provider-object pivot). The prose round (`3e3c9ae`, seat-authorized) rewrote those two sentences so each reads true at the head; its front matter is byte-identical. - **Changeset name.** It is `10528-grid-object-table-fetch.md`, not the dispatched `10528-grid-object-table-pivot-fetch.md`; the name dates from round 1. It now covers both halves, and it is kept as is because the presence gate passes with it. - **The grid's `dataSource` prop.** The grid hands its `dataSource` prop to `DatasetWidget` only. The `object-*` nodes it composes, the new table node included, read the adapter from `SchemaRendererContext`. `DashboardRenderer` also passes its prop to each child `SchemaRenderer`. Not measured as reachable: no host in this repo mounts the grid outside a provider. Carrier: none. Noted, not filed. - **`DashboardRenderer`'s pivot-arm comment.** It says the grid "does still draw pivots from static data and from the `provider: 'object'` config". In effect, the second half has been false: the grid draws an empty pivot and sends no query. This belongs to the pivot decision. - **Docs.** No README or `content/docs` statement covers the grid's object-bound tables (checked by grepping for `dashboard-grid` and `DashboardGridLayout`), so no doc edit is owed. Session: `https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy` --- _Generated by [Claude Code](https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cc07476 commit 668dd59

6 files changed

Lines changed: 383 additions & 24 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@object-ui/plugin-dashboard': patch
3+
---
4+
5+
Fix: the editable dashboard grid (`dashboard-grid`, `DashboardGridLayout`) now
6+
treats widgets whose data is `{ provider: 'object', object }` the way the
7+
read-only `DashboardRenderer` already does (objectui#10528). Before this change
8+
the grid turned a table, list or pivot widget with that config into a static
9+
node with `data: []`. The tile showed an empty table or cross-tab and sent no
10+
query.
11+
12+
**Table and list widgets now fetch their rows.** The grid builds the same node
13+
`DashboardRenderer` builds for the widget: an `object-data-table`, which loads
14+
the object's rows through the data source. The props match too:
15+
16+
- the provider's `filter` (or the widget's `filter`) reaches the query;
17+
- the widget's `searchable` and `pagination` are honoured, and a `list` never
18+
gets them;
19+
- a row click opens the record in a drawer by default, as on the read-only
20+
dashboard. `options.drillDown` still overrides this. In edit mode, dragging
21+
still starts only from the drag handle.
22+
23+
**Pivot widgets with that config now show the retired-widget placeholder.**
24+
It is the same placeholder `DashboardRenderer` already shows for them ("This
25+
widget uses a retired data format. Edit it to bind a dataset."). Under ADR-0021
26+
a pivot is bound to a dataset, and a dataset-bound pivot still renders as
27+
before.
28+
29+
Widgets bound to inline rows are unchanged, including static-data pivots.

‎.changeset/7353-retire-widget-dataprovider.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ as `dataProvider`, next to `objectName`. The widgets read only `objectName`, so
1515
version policy).** Compared with the released 17.6.0:
1616

1717
- `DashboardRenderer` no longer writes `dataProvider` on the `object-data-table`
18-
node, and `DashboardGridLayout` no longer writes it on the `data-table` and
19-
`pivot` nodes. All three still write `objectName`, and the object-backed table
20-
widget still fetches through it.
18+
node, and `DashboardGridLayout` no longer writes it on any node it builds for a
19+
`provider: 'object'` widget. Since objectui#10528 the grid builds that same
20+
self-fetching `object-data-table` node for such a table widget, and shows the
21+
retired-widget placeholder for such a pivot widget. The `object-data-table`
22+
node still writes `objectName` on both surfaces, and the object-backed table
23+
widget fetches through it.
2124
- In 17.6.0, the `schema` prop types of `ObjectPivotTable` and `ObjectDataTable`
2225
both declare `dataProvider?: { provider: string; object?: string }`. Both now
2326
declare it as a retirement tombstone (`dataProvider?: never`). TypeScript code

‎packages/plugin-dashboard/src/DashboardGridLayout.tsx‎

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -376,16 +376,29 @@ export const DashboardGridLayout: React.FC<DashboardGridLayoutProps> = ({
376376
if (dispatch.family === 'table') {
377377
const widgetData = (widget as any).data || options.data;
378378

379-
// provider: 'object' — pass through object config for async data loading
379+
// provider: 'object' — ObjectDataTable fetches the rows (objectui#10528).
380+
// This arm used to emit a STATIC `data-table` with `data: []` and an
381+
// `objectName` that `data-table` never reads, so the tile drew an empty
382+
// table and issued no query, while `DashboardRenderer` fetched and drew
383+
// the same stored widget. It now emits the node that renderer's table arm
384+
// emits, prop for prop: the self-fetching `object-data-table`, the
385+
// provider's `filter`, the declared `searchable` / `pagination` (never on
386+
// a `list`), and default-on drill-to-record. The drill cannot fight the
387+
// editor: dragging starts only from the `.drag-handle` element (see
388+
// `dragConfig` below), and this grid has no widget selection. The two
389+
// copies are held equal by
390+
// `DashboardGridLayout.objectProviderFetch-10528.test.tsx`.
380391
if (isObjectProvider(widgetData)) {
392+
const isList = widgetType === 'list';
381393
const { data: _data, ...restOptions } = options;
382394
return {
383-
type: 'data-table',
395+
type: 'object-data-table',
384396
...restOptions,
385397
objectName: widgetData.object,
386-
data: [],
387-
searchable: false,
388-
pagination: false,
398+
filter: widgetData.filter || widget.filter,
399+
searchable: isList ? false : (widget.searchable ?? false),
400+
pagination: isList ? false : (widget.pagination ?? false),
401+
drillDown: options.drillDown ?? { enabled: true, mode: 'record' as const },
389402
className: "border-0"
390403
};
391404
}
@@ -414,16 +427,20 @@ export const DashboardGridLayout: React.FC<DashboardGridLayoutProps> = ({
414427
if (dispatch.family === 'pivot') {
415428
const widgetData = (widget as any).data || options.data;
416429

417-
// provider: 'object' — pass through object config for async data loading
418-
if (isObjectProvider(widgetData)) {
419-
const { data: _data, ...restOptions } = options;
420-
return {
421-
type: 'pivot',
422-
...restOptions,
423-
objectName: widgetData.object,
424-
data: [],
425-
};
426-
}
430+
// provider: 'object' — RETIRED, with the same placeholder object
431+
// `DashboardRenderer`'s pivot arm returns (objectui#10528). A pivot is a
432+
// cross-tab, and ADR-0021 puts cross-tabs on the dataset layer only: the
433+
// shared dispatch types this family "dataset-bound only; a non-dataset
434+
// pivot is stale metadata" (`widgetDispatch.ts`). This branch used to emit
435+
// a static `pivot` node with `data: []` and an `objectName` that
436+
// `PivotTable` never reads, so the tile drew an empty cross-tab and sent
437+
// no query, while the read dashboard showed this placeholder for the same
438+
// widget. Mapping it to the self-fetching `object-pivot` instead would
439+
// have revived the removed inline analytics shape in the editor alone.
440+
// A dataset-bound pivot still renders through `DatasetWidget` (the fork
441+
// at the render site below), and the static-data pivot under this branch
442+
// is unchanged.
443+
if (isObjectProvider(widgetData)) return LEGACY_RETIRED_WIDGET_SCHEMA;
427444

428445
return {
429446
type: 'pivot',

‎packages/plugin-dashboard/src/DashboardRenderer.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -826,11 +826,13 @@ const DashboardRendererInner = forwardRef<HTMLDivElement, DashboardRendererProps
826826
// This arm stays surface-LOCAL and is deliberately not part of the
827827
// shared detector (objectui#4612): it is a statement about what THIS
828828
// surface can draw — nothing here emits a pivot block — not about the
829-
// widget being legacy. `DashboardGridLayout` does still draw pivots from
830-
// static data and from the `provider: 'object'` config, so exporting
831-
// this family-wide arm would have retired two live branches over there.
832-
// The legacy pivot SHAPE is covered on both surfaces by the shared
833-
// sentinel above, which its top-level `object` matches.
829+
// widget being legacy. `DashboardGridLayout` still draws pivots from
830+
// static data, so exporting this family-wide arm would retire that
831+
// live branch over there. Its `provider: 'object'` pivot answers with
832+
// this same placeholder (objectui#10528), imported from the same
833+
// module rather than restated. The legacy pivot SHAPE is covered on
834+
// both surfaces by the shared sentinel above, which its top-level
835+
// `object` matches.
834836
if (dispatch.family === 'pivot') {
835837
return LEGACY_RETIRED_WIDGET_SCHEMA;
836838
}

0 commit comments

Comments
 (0)