diff --git a/.changeset/25994-objectgrid-diagnostics-hook.md b/.changeset/25994-objectgrid-diagnostics-hook.md new file mode 100644 index 00000000000..34ee38d3717 --- /dev/null +++ b/.changeset/25994-objectgrid-diagnostics-hook.md @@ -0,0 +1,12 @@ +--- +'@object-ui/plugin-grid': patch +--- + +refactor(plugin-grid): `ObjectGrid`'s authored-input diagnostics move into a sibling hook + +Clause-②: no +Metadata-protocol: none + +The three `console.warn` effects that report an unreadable `columns` entry, an unusable `bulkActionDefs` member and a non-positive page size move verbatim out of `ObjectGrid.tsx` into `useObjectGridDiagnostics` (`objectGridDiagnostics.ts`). `ObjectGrid` calls the hook where the effects stood, so its hook order, its warnings and its rendered output do not change. No name is renamed, and the package entry is unchanged. + +What to do: nothing. diff --git a/ui/packages/plugin-grid/src/ObjectGrid.tsx b/ui/packages/plugin-grid/src/ObjectGrid.tsx index 1cdbb5f3d09..cda41ab8300 100644 --- a/ui/packages/plugin-grid/src/ObjectGrid.tsx +++ b/ui/packages/plugin-grid/src/ObjectGrid.tsx @@ -79,9 +79,9 @@ import { resolveRowCrudAffordances, resolveRowRecordCrudAffordance } from './row import { useRecordCrudVerdicts } from './hooks/useRecordCrudVerdicts'; import { resolveLegacyRowActions } from './resolveLegacyRowActions'; import { applyRelationalMeta } from './relationalMetaKeys'; -import { resolveBulkActions, describeUnusableBulkActionDefs, resolvePromotedBulkParams } from './resolveBulkActions'; +import { resolveBulkActions, resolvePromotedBulkParams } from './resolveBulkActions'; import { partitionBulkRows } from './bulkEligibility'; -import { resolvesToDataColumn, describeUnresolvedColumns } from './columnSpellingDiagnostics'; +import { resolvesToDataColumn } from './columnSpellingDiagnostics'; import { RowActionMenu, formatActionLabel, planRowActionMenu } from './components/RowActionMenu'; import { BulkActionBar } from './components/BulkActionBar'; import { BulkActionDialog } from './components/BulkActionDialog'; @@ -91,10 +91,11 @@ import { findParamsSignature, useResolvedGridFilters } from './objectGridQuery'; import { type ObjectGridColumn, type ObjectGridColumnDraft, isChipFieldType, normalizeColumns, resolveColumnLabel } from './objectGridColumns'; import { LinkCell, recordPointerFace, rowRecordId } from './objectGridLinkCell'; import { useGridTranslation } from './objectGridTranslations'; -import { DEFAULT_DISPLAY_PAGE_SIZE, DEFAULT_FETCH_BATCH_SIZE, DEFAULT_SELECTION_TYPE, NO_GROUPING_REFUSAL, type RowHeightMode, describeNonPositivePageSize, resolvePageSize, resolveRowHeightMode } from './objectGridDefaults'; +import { DEFAULT_DISPLAY_PAGE_SIZE, DEFAULT_FETCH_BATCH_SIZE, DEFAULT_SELECTION_TYPE, NO_GROUPING_REFUSAL, type RowHeightMode, resolvePageSize, resolveRowHeightMode } from './objectGridDefaults'; import { type BatchSaveOutcome, OCC_TOKEN_FIELD, type RowConflictChoice, type WrittenRowVersion, rowVersionOf, rowVersionToSend } from './objectGridRowVersion'; import { type ObjectGridColumnState, type ObjectGridExternalPaginationProps, type ObjectGridFooterSummary } from './objectGridProps'; import { FooterSummaryReport } from './objectGridFooterSummaryReport'; +import { useObjectGridDiagnostics } from './objectGridDiagnostics'; export type { RetiredListColumnKey, ObjectGridColumnHolds, ObjectGridColumnDraft, ObjectGridColumn } from './objectGridColumns'; export type { ObjectGridColumnState, ObjectGridFooterSummary, ObjectGridExternalPaginationProps } from './objectGridProps'; @@ -2624,84 +2625,9 @@ export const ObjectGrid: React.FC = ({ wholeSetSummary.row, ); - // An authored column the renderer cannot read now says so, instead of just - // not being there (objectui#5349 — the Q2 objectui#5068 deferred). - // - // #5068 made `ListColumnSchema`'s `field` / `label` the only spelling read - // here, which is right; what it left behind was the receipt. A column - // authored `{ accessorKey, header }` (or with no `field` at all) is dropped - // by `resolvesToDataColumn` above, and until this effect landed the author - // saw no error, no warning and no empty header — just a grid with its - // row-number column and no data columns. Same defect shape #5068 exists to - // fix, one level down: renderer and author disagree, author gets a success - // receipt. - // - // Scope, deliberately narrow: this reads the `columns` INPUT and nothing - // else. It never asks whether the grid found ROWS, because a grid legitimately - // draws them from five different places (inline `data` array, `data.provider: - // 'value'`, legacy `staticData`, `bind`, or a host that owns the fetch and - // passes the window down as a `data` React prop). A predicate that consulted - // those would eventually paint a configuration error over a working grid. - // `hidden: true` is authored intent and is never reported. - // - // Channel: the same one `ObjectGrid` already uses for "you declared it, the - // renderer dropped it" — a `useEffect` keyed on the schema slice and one - // `console.warn` prefixed `[ObjectUI] ObjectGrid :` (see the export - // format warning below) — rather than a second, differently-shaped one. - const columnDiagnosticBlockType = (schema as { type?: unknown }).type; - const columnDiagnosticLabel = schema.label ?? (schema as { title?: unknown }).title; - useEffect(() => { - const message = describeUnresolvedColumns(schema.columns, { - blockType: columnDiagnosticBlockType, - objectName: schema.objectName, - label: columnDiagnosticLabel, - }); - if (message) console.warn(message); - }, [schema.columns, columnDiagnosticBlockType, schema.objectName, columnDiagnosticLabel]); - - // [objectui#8730] The same channel, for the sibling failure on - // `bulkActionDefs`. A member that is not a usable def (a bare action name — - // the OTHER key's vocabulary — or `null`, a number, `{}`, `{ name: '' }`) is - // skipped by `resolveBulkActions`; before that guard it reached the bar and - // `formatActionLabel(undefined)` threw during render, so the author's first - // multi-row selection lost the whole selection bar. - // - // The skip alone would only relocate the failure into silence — which is what - // the mirror direction already does (`bulkActions: [{ name: 'approve' }]`, - // stepped over by the `typeof name !== 'string'` guard). Saying which member - // was dropped, and that a bare name belongs in `bulkActions`, is what makes - // this a diagnosis rather than a quieter version of the same defect. One - // `console.warn` per authored array, keyed on it — NOT a second guard: the - // predicate lives once, in `resolveBulkActions`, and this reads it. - const bulkDefsDiagnosticSlice = (schema as { bulkActionDefs?: unknown }).bulkActionDefs; - useEffect(() => { - const message = describeUnusableBulkActionDefs(bulkDefsDiagnosticSlice, { - blockType: columnDiagnosticBlockType, - objectName: schema.objectName, - label: columnDiagnosticLabel, - }); - if (message) console.warn(message); - }, [bulkDefsDiagnosticSlice, columnDiagnosticBlockType, schema.objectName, columnDiagnosticLabel]); - - // [objectui#9853] The same channel again, for a `pagination.pageSize` (or the - // deprecated flat shorthand) that is not a positive integer. `resolvePageSize` - // drops such a value at all three read points and uses the site's default; on - // its own that is a quieter version of the defect, because substituting a - // number the author never wrote is exactly what the flat site already did in - // silence. This is the half that makes it a diagnosis. - // - // Keyed on the authored slice, so it is one warning per declaration and not - // one per render. NOT a second guard — the predicate lives once, in - // `isUsablePageSize`, and this reads it. - const pageSizeDiagnosticObject = schema.pagination; - const pageSizeDiagnosticFlat = schema.pageSize; - useEffect(() => { - const message = describeNonPositivePageSize( - { pagination: pageSizeDiagnosticObject, pageSize: pageSizeDiagnosticFlat }, - { blockType: columnDiagnosticBlockType, objectName: schema.objectName }, - ); - if (message) console.warn(message); - }, [pageSizeDiagnosticObject, pageSizeDiagnosticFlat, columnDiagnosticBlockType, schema.objectName]); + // The authored-input diagnostics (`columns`, `bulkActionDefs`, page size), + // three `console.warn` effects, run here in hook order (`./objectGridDiagnostics`). + useObjectGridDiagnostics(schema); const generateColumns = useCallback((): ObjectGridColumnDraft[] => { // objectui#11815 — the host's verdict on a declared deadline's diff --git a/ui/packages/plugin-grid/src/objectGridDiagnostics.ts b/ui/packages/plugin-grid/src/objectGridDiagnostics.ts new file mode 100644 index 00000000000..e4c41ae1ed7 --- /dev/null +++ b/ui/packages/plugin-grid/src/objectGridDiagnostics.ts @@ -0,0 +1,98 @@ +/** + * The authored-input diagnostics of `ObjectGrid` (`./ObjectGrid.tsx`): the three + * `console.warn` effects that report an unreadable `columns` entry, an unusable + * `bulkActionDefs` member and a non-positive page size, moved out of that file + * verbatim into a hook (objectstack#25994, slice 3). + */ + +import { useEffect } from 'react'; +import type { ObjectGridSchema } from '@object-ui/types'; +import { describeUnresolvedColumns } from './columnSpellingDiagnostics'; +import { describeUnusableBulkActionDefs } from './resolveBulkActions'; +import { describeNonPositivePageSize } from './objectGridDefaults'; + +/** + * Runs `ObjectGrid`'s authored-input diagnostics. It declares no state and + * returns nothing. `ObjectGrid` calls it where the three effects used to stand, + * so the order of the component's hooks does not change. + */ +export function useObjectGridDiagnostics(schema: ObjectGridSchema): void { + // An authored column the renderer cannot read now says so, instead of just + // not being there (objectui#5349 — the Q2 objectui#5068 deferred). + // + // #5068 made `ListColumnSchema`'s `field` / `label` the only spelling read + // here, which is right; what it left behind was the receipt. A column + // authored `{ accessorKey, header }` (or with no `field` at all) is dropped + // by `resolvesToDataColumn` above, and until this effect landed the author + // saw no error, no warning and no empty header — just a grid with its + // row-number column and no data columns. Same defect shape #5068 exists to + // fix, one level down: renderer and author disagree, author gets a success + // receipt. + // + // Scope, deliberately narrow: this reads the `columns` INPUT and nothing + // else. It never asks whether the grid found ROWS, because a grid legitimately + // draws them from five different places (inline `data` array, `data.provider: + // 'value'`, legacy `staticData`, `bind`, or a host that owns the fetch and + // passes the window down as a `data` React prop). A predicate that consulted + // those would eventually paint a configuration error over a working grid. + // `hidden: true` is authored intent and is never reported. + // + // Channel: the same one `ObjectGrid` already uses for "you declared it, the + // renderer dropped it" — a `useEffect` keyed on the schema slice and one + // `console.warn` prefixed `[ObjectUI] ObjectGrid :` (see the export + // format warning below) — rather than a second, differently-shaped one. + const columnDiagnosticBlockType = (schema as { type?: unknown }).type; + const columnDiagnosticLabel = schema.label ?? (schema as { title?: unknown }).title; + useEffect(() => { + const message = describeUnresolvedColumns(schema.columns, { + blockType: columnDiagnosticBlockType, + objectName: schema.objectName, + label: columnDiagnosticLabel, + }); + if (message) console.warn(message); + }, [schema.columns, columnDiagnosticBlockType, schema.objectName, columnDiagnosticLabel]); + + // [objectui#8730] The same channel, for the sibling failure on + // `bulkActionDefs`. A member that is not a usable def (a bare action name — + // the OTHER key's vocabulary — or `null`, a number, `{}`, `{ name: '' }`) is + // skipped by `resolveBulkActions`; before that guard it reached the bar and + // `formatActionLabel(undefined)` threw during render, so the author's first + // multi-row selection lost the whole selection bar. + // + // The skip alone would only relocate the failure into silence — which is what + // the mirror direction already does (`bulkActions: [{ name: 'approve' }]`, + // stepped over by the `typeof name !== 'string'` guard). Saying which member + // was dropped, and that a bare name belongs in `bulkActions`, is what makes + // this a diagnosis rather than a quieter version of the same defect. One + // `console.warn` per authored array, keyed on it — NOT a second guard: the + // predicate lives once, in `resolveBulkActions`, and this reads it. + const bulkDefsDiagnosticSlice = (schema as { bulkActionDefs?: unknown }).bulkActionDefs; + useEffect(() => { + const message = describeUnusableBulkActionDefs(bulkDefsDiagnosticSlice, { + blockType: columnDiagnosticBlockType, + objectName: schema.objectName, + label: columnDiagnosticLabel, + }); + if (message) console.warn(message); + }, [bulkDefsDiagnosticSlice, columnDiagnosticBlockType, schema.objectName, columnDiagnosticLabel]); + + // [objectui#9853] The same channel again, for a `pagination.pageSize` (or the + // deprecated flat shorthand) that is not a positive integer. `resolvePageSize` + // drops such a value at all three read points and uses the site's default; on + // its own that is a quieter version of the defect, because substituting a + // number the author never wrote is exactly what the flat site already did in + // silence. This is the half that makes it a diagnosis. + // + // Keyed on the authored slice, so it is one warning per declaration and not + // one per render. NOT a second guard — the predicate lives once, in + // `isUsablePageSize`, and this reads it. + const pageSizeDiagnosticObject = schema.pagination; + const pageSizeDiagnosticFlat = schema.pageSize; + useEffect(() => { + const message = describeNonPositivePageSize( + { pagination: pageSizeDiagnosticObject, pageSize: pageSizeDiagnosticFlat }, + { blockType: columnDiagnosticBlockType, objectName: schema.objectName }, + ); + if (message) console.warn(message); + }, [pageSizeDiagnosticObject, pageSizeDiagnosticFlat, columnDiagnosticBlockType, schema.objectName]); +}