diff --git a/.changeset/11068-grid-declared-keys.md b/.changeset/11068-grid-declared-keys.md index b87729c146..0645008bf5 100644 --- a/.changeset/11068-grid-declared-keys.md +++ b/.changeset/11068-grid-declared-keys.md @@ -38,5 +38,5 @@ now authors only keys the grid reads. refused by name. **Unchanged.** An `object-view`'s `table` slot still withholds `description` and `emptyState`. -Write them on the `object-grid` node itself. `showFilters` and `keyboardNavigation` are still -declared and still not read. +Write them on the `object-grid` node itself. `keyboardNavigation` is still declared and still +not read. diff --git a/.changeset/11068-object-grid-show-filters.md b/.changeset/11068-object-grid-show-filters.md new file mode 100644 index 0000000000..14ffe4a0dc --- /dev/null +++ b/.changeset/11068-object-grid-show-filters.md @@ -0,0 +1,26 @@ +--- +'@object-ui/types': minor +--- + +`showFilters` is retired on `object-grid` (objectui#11068). + +**BREAKING (authoring).** An `object-grid` that carries `showFilters` now fails validation, +and TypeScript code that writes it on an `ObjectGridSchema` no longer compiles. Delete the key. + +**What was wrong.** `ObjectGridSchema` declared `showFilters`, but an `object-grid` has no filter +UI and never read the key. An author who wrote `showFilters: true` on a grid got no type error, +no validator refusal and no filter. + +**Where filtering lives.** The filter builder is the `list-view` toolbar's. Author a `list-view` +and switch the builder with its `userActions.filter`. To narrow the rows a grid fetches, write +`filter` on the grid. + +**How it is refused.** The key is `?: never` on the `ObjectGridSchema` interface and a named +refusal on its Zod twin. On both authoring faces, the node face and the strict authoring face, +the message names the key and `userActions.filter`. This matches the upstream protocol, whose +`object-grid` row does not declare the key either. + +**Unchanged.** An `object-view`'s own `showFilters` and a `list-view`'s are different keys, and +both are still accepted. `ObjectView` reads its own, and `ListView` folds its own onto +`userActions.filter`. An `object-view`'s `table` slot already refused `showFilters` and still +does. diff --git a/README.md b/README.md index 060d9777ec..2f3fe1794c 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,6 @@ published is not an error: the expression is returned as its own source text. { "field": "created_at", "label": "Joined" } ], "showSearch": true, - "showFilters": true, "operations": { "create": true, "read": true, "update": true, "delete": true, "export": true } } ``` diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md index e4c7384c59..8ac771a457 100644 --- a/content/docs/api/schema-reference.md +++ b/content/docs/api/schema-reference.md @@ -766,9 +766,9 @@ A data grid that auto-fetches from an ObjectQL object definition. Includes searc ``` Every key in this example is one the grid reads (objectui#11068). It used to -also author `showFilters`, `striped` and `operations.read`, which nothing reads, -and `selection: { enabled, mode }` and `pagination.enabled`, which the validator -refuses: selection is spelled `selection: { type: 'multiple' }` (`'none'`, +also author `showFilters` (now retired on this node, below), `striped` and +`operations.read`, which nothing reads, and `selection: { enabled, mode }` and +`pagination.enabled`, which the validator refuses: selection is spelled `selection: { type: 'multiple' }` (`'none'`, `'single'` or `'multiple'`), and `pagination` declares no on switch — its presence enables paging. @@ -798,6 +798,14 @@ presence enables paging. > `@object-ui/types` refuse them by name. Write `rowActions`, `bulkActions`, `id` > or `label`, and `emptyState: { "message": … }` instead. +> **`showFilters` is retired on this node too (objectui#11068).** An `object-grid` +> has no filter UI, and nothing read the key. The filter builder is the +> `list-view` toolbar's: author a `list-view` node and switch it with +> `"userActions": { "filter": true }`. To narrow the rows a grid fetches, write +> `filter`. Both faces of `@object-ui/types` refuse an authored `showFilters` on an +> `object-grid` by name. An [`object-view`](#objectviewschema)'s own `showFilters`, +> below, is a different key and is unchanged. + > **`defaultSort` is retired (objectui#5861).** `ObjectGridSchema` used to accept a > legacy single-entry `defaultSort: { field, order }` beside `sort`. The installed > `@objectstack/spec` protocol refuses it by name (a retired-key tombstone), and no diff --git a/content/docs/plugins/plugin-grid.mdx b/content/docs/plugins/plugin-grid.mdx index ff83408940..57db1c2b88 100644 --- a/content/docs/plugins/plugin-grid.mdx +++ b/content/docs/plugins/plugin-grid.mdx @@ -135,6 +135,12 @@ this node (objectui#11068): nothing ever read them, and both faces of `@object-ui/types` refuse them by name. Write `id` or `label`, `emptyState: { message }`, `rowActions` and `bulkActions` instead. +`showFilters` is **retired** on this node too (objectui#11068). The grid has no +filter UI, and nothing read the key. The filter builder is the `list-view` +toolbar's, switched by its `userActions.filter`; to narrow the rows a grid +fetches, write `filter`. An `object-view`'s own `showFilters` is a different key +and is unchanged. + ### Column Definition A column is either a **field name** (`"name"`) or a `ListColumn` object. diff --git a/packages/plugin-grid/src/__tests__/ObjectGrid.declaredKeys-11068.test.tsx b/packages/plugin-grid/src/__tests__/ObjectGrid.declaredKeys-11068.test.tsx index d772332e04..a81bb6b302 100644 --- a/packages/plugin-grid/src/__tests__/ObjectGrid.declaredKeys-11068.test.tsx +++ b/packages/plugin-grid/src/__tests__/ObjectGrid.declaredKeys-11068.test.tsx @@ -10,16 +10,18 @@ * Every key `ObjectGridSchema` declares takes effect or is retired * (objectui#11068) — the RENDERER half. The declaration half (the four * tombstones and the `emptyState` mirror) is `packages/types`' - * `object-grid-declared-keys-11068.test.ts`. + * `object-grid-declared-keys-11068.test.ts`; the fifth tombstone, `showFilters`, + * is `object-grid-show-filters-retired-11068.test.ts` there. * * 1. `description` draws one line of help text above the grid, resolved like * `label` — a locale map against the display locale. * 2. `emptyState` draws in place of an empty table — but not when a term in * the grid's own server-side search box emptied it, where the table and its * search box must stay. - * 3. `name`, `placeholder`, `rowSpecActions` and `bulkSpecActions` — the four - * keys the card retired — draw nothing. Asked with a RULER, the same - * document drawn twice and compared as bytes, because `ObjectGridRenderer` + * 3. `name`, `placeholder`, `rowSpecActions`, `bulkSpecActions` and + * `showFilters` — the five keys the card retired — draw nothing. Asked + * with a RULER, the same document drawn twice and compared as bytes, + * because `ObjectGridRenderer` * hands a node's leftover keys on as props and a source grep cannot see * that channel (AGENTS.md, "a source grep's zero cannot answer this"). * @@ -172,7 +174,7 @@ describe('`emptyState` draws in place of an empty table (objectui#11068)', () => }); }); -/* ── 3. The four retired keys draw nothing ────────────────────────────────── */ +/* ── 3. The five retired keys draw nothing ────────────────────────────────── */ /** Draw one document and return its markup, once the rows are on screen. */ async function draw(schema: Record): Promise { @@ -198,13 +200,17 @@ describe('the ruler', () => { }); }); -describe('the four keys objectui#11068 retired change nothing the grid draws', () => { +describe('the five keys objectui#11068 retired change nothing the grid draws', () => { it.each([ ['name', 'all_contacts'], ['placeholder', 'Nothing here yet'], ['rowSpecActions', ['edit', 'delete']], ['bulkSpecActions', ['delete']], - ])('`%s`', async (key, value) => { + // Retired after triage's retriage answer: the grid has no filter UI, so + // neither value draws anything — the filter builder is `list-view`'s. + ['showFilters', true], + ['showFilters', false], + ])('`%s` (%j)', async (key, value) => { const a = await draw(grid(ROWS)); cleanup(); const b = await draw(grid(ROWS, { [key]: value })); diff --git a/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx b/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx index 124d6088cb..1100fbc307 100644 --- a/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx +++ b/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx @@ -29,8 +29,11 @@ * draw identical bytes (the ruler is stable — no timestamps, no random ids), the * second is a LIT CONTROL on a key the renderer demonstrably DOES read, and only * then is the measurement taken. It is taken twice: once on a plain grid and - * once with the filter surface on, since `operators` is a filtering word and a - * grid without a filter affordance would be the wrong corpus to ask. + * once with the grid's search box on (`searchableFields`), since `operators` is + * a filtering word and a grid without its one query affordance would be the + * wrong corpus to ask. That corpus also used to carry `showFilters: true`, which + * never drew anything: an `object-grid` has no filter UI, and objectui#11068 + * retired the key on this node. * * ⚠️ The claim is bounded by the ruler: this is what the RENDERED output does * with the key, on these documents. ⛔ It is not a claim that no code anywhere @@ -88,11 +91,11 @@ describe('an authored `operators`', () => { expect(b).toBe(a); }); - it('changes nothing with the filter surface on either', async () => { - const FILTERS = { ...BASE, showFilters: true, searchableFields: ['name'] }; - const a = await draw({ ...FILTERS }); + it('changes nothing with the search box on either', async () => { + const SEARCH = { ...BASE, searchableFields: ['name'] }; + const a = await draw({ ...SEARCH }); cleanup(); - const b = await draw({ ...FILTERS, operators: { name: ['equals', 'contains'] } }); + const b = await draw({ ...SEARCH, operators: { name: ['equals', 'contains'] } }); expect(b).toBe(a); }); }); diff --git a/packages/types/src/__tests__/object-grid-show-filters-retired-11068.test.ts b/packages/types/src/__tests__/object-grid-show-filters-retired-11068.test.ts new file mode 100644 index 0000000000..f12d0aeaa6 --- /dev/null +++ b/packages/types/src/__tests__/object-grid-show-filters-retired-11068.test.ts @@ -0,0 +1,191 @@ +/** + * 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. + */ + +/** + * `showFilters` is retired on `object-grid` (objectui#11068, triage's answer to + * the card's retriage) — the DECLARATION half. The renderer half, which shows an + * authored `showFilters` draws nothing, is `plugin-grid`'s + * `ObjectGrid.declaredKeys-11068.test.tsx`. + * + * ## Why a tombstone, and why it points at the list view + * + * `ObjectGrid` has no filter UI. The one filter surface objectui draws is the + * `list-view` toolbar's filter builder, switched by `userActions.filter`, so + * honouring the key on the grid would have meant building a second filter + * surface. The upstream protocol's `object-grid` row does not declare the key + * either. Deleting the member would refuse nothing: the zod twin ends + * `.passthrough()` and the interface inherits `BaseSchema`'s index signature. + * So the key stays DECLARED and unwritable on both faces — `retirementTombstone()` + * and `?: never` — as `rowSpecActions` and `defaultSort` are. + * + * ## The two authoring faces, and the controls beside each refusal + * + * The node face (`safeValidateSchema`) and the strict authoring face + * (`StrictAnyComponentSchema`) are both asked. A red parse proves a by-name + * refusal only beside a document that stays green on the same instrument: the + * grid without the key, a list view's own `showFilters` and `userActions.filter`, + * and an object view's own `showFilters` — which `ObjectView` still reads, so it + * is not retired here. + */ + +import { describe, it, expect } from 'vitest'; +import type { z } from 'zod'; +import { ObjectGridPropsSchema as SpecObjectGridPropsSchema, ListViewSchema as SpecListViewSchema } from '@objectstack/spec/ui'; +import { ObjectGridSchema } from '../zod/objectql.zod.js'; +// Both faces through the barrel: objectui#8345 pins it as the only entry into +// the strict face's module cycle, test files included. +import { safeValidateSchema, StrictAnyComponentSchema } from '../zod/index.zod.js'; +import type { + ListViewSchema as ListViewSchemaType, + ObjectGridSchema as ObjectGridSchemaType, + ObjectViewSchema as ObjectViewSchemaType, +} from '../objectql.js'; + +/** A control key no surface in this package or the protocol declares. */ +const UNKNOWN_KEY = 'zzzNotAKeyAnySurfaceDeclares11068'; + +/** The minimum green `object-grid` document. */ +const GRID = { type: 'object-grid' as const, objectName: 'probe' }; + +type Issue = { code: string; path: PropertyKey[]; message: string; errors?: Issue[][] }; + +/** The two authoring faces, each as a `safeParse`. */ +const FACES = [ + ['node face (safeValidateSchema)', (doc: unknown) => safeValidateSchema(doc)], + ['strict authoring face (StrictAnyComponentSchema)', (doc: unknown) => StrictAnyComponentSchema.safeParse(doc)], +] as const; + +type Parse = (doc: unknown) => z.ZodSafeParseResult; + +/** Every issue in the tree, union arms included, with its full path. */ +function flatten(issues: Issue[] | undefined, prefix: PropertyKey[] = []): Issue[] { + const out: Issue[] = []; + for (const issue of issues ?? []) { + const path = [...prefix, ...issue.path]; + out.push({ ...issue, path }); + for (const arm of issue.errors ?? []) out.push(...flatten(arm, path)); + } + return out; +} + +function issueAt(parse: Parse, doc: unknown, path: string): Issue | undefined { + const result = parse(doc); + if (result.success) return undefined; + return flatten(result.error.issues as unknown as Issue[]).find((i) => i.path.map(String).join('.') === path); +} + +describe('an authored `showFilters` on `object-grid` is refused BY NAME on both authoring faces (objectui#11068)', () => { + it.each(FACES)('%s refuses it at its own path, whatever the value', (_face, parse) => { + for (const value of [true, false]) { + const result = parse({ ...GRID, showFilters: value }); + expect(result.success, `showFilters: ${value} parsed green`).toBe(false); + const issue = issueAt(parse, { ...GRID, showFilters: value }, 'showFilters'); + expect(issue, `no issue at showFilters for ${value}`).toBeDefined(); + // `retirementTombstone()` customises the MESSAGE only; the code is a bare `z.never()`'s. + expect(issue?.code).toBe('invalid_type'); + } + }); + + it.each(FACES)('%s names the card and the list view’s `userActions.filter`', (_face, parse) => { + const message = String(issueAt(parse, { ...GRID, showFilters: true }, 'showFilters')?.message); + expect(message).toContain('objectui#11068'); + expect(message).toContain('`userActions.filter`'); + expect(message).toContain('`list-view`'); + }); + + it.each(FACES)('LIT CONTROL — %s accepts the same grid without the key', (_face, parse) => { + const result = parse(GRID); + expect(result.success, result.success ? '' : JSON.stringify(result.error.issues)).toBe(true); + }); + + it('LIT CONTROL — the node face still keeps an unrecognised key on the same document', () => { + // Without this, the refusal above would be consistent with the whole object + // having turned strict on the node face — a far larger change than the one made. + expect(safeValidateSchema({ ...GRID, [UNKNOWN_KEY]: true }).success).toBe(true); + }); + + it('writes the SAME string into the metadata channel as into the parse message', () => { + const described = (ObjectGridSchema.shape.showFilters as { description?: string }).description; + const issue = ObjectGridSchema.safeParse({ ...GRID, showFilters: true }); + expect(issue.success).toBe(false); + const message = issue.success ? undefined : issue.error.issues.find((i) => i.path.join('.') === 'showFilters')?.message; + expect(described).toBe(message); + }); + + it('the tombstone is a MEMBER, not a deletion — that is what makes the refusal loud', () => { + expect('showFilters' in ObjectGridSchema.shape).toBe(true); + expect(UNKNOWN_KEY in ObjectGridSchema.shape).toBe(false); + }); +}); + +describe('`showFilters` stays live where a renderer reads it (the controls the retirement must not touch)', () => { + const LIST = { type: 'list-view' as const, objectName: 'probe', columns: ['name'] }; + + it.each(FACES)('%s accepts the list view’s `userActions.filter` — the spelling the refusal names', (_face, parse) => { + const result = parse({ ...LIST, userActions: { filter: true } }); + expect(result.success, result.success ? '' : JSON.stringify(result.error.issues)).toBe(true); + }); + + it.each(FACES)('%s accepts a list view’s own `showFilters`', (_face, parse) => { + const result = parse({ ...LIST, showFilters: true }); + expect(result.success, result.success ? '' : JSON.stringify(result.error.issues)).toBe(true); + }); + + it.each(FACES)('%s accepts an object view’s own `showFilters`', (_face, parse) => { + const result = parse({ type: 'object-view', objectName: 'probe', showFilters: true }); + expect(result.success, result.success ? '' : JSON.stringify(result.error.issues)).toBe(true); + }); +}); + +describe('the UPSTREAM half, re-derived from the installed pin', () => { + it('upstream `object-grid` does not declare `showFilters`, and refuses it', () => { + expect('showFilters' in SpecObjectGridPropsSchema.shape).toBe(false); + expect(SpecObjectGridPropsSchema.safeParse({ objectName: 'probe' }).success).toBe(true); + expect(SpecObjectGridPropsSchema.safeParse({ objectName: 'probe', showFilters: true }).success).toBe(false); + }); + + it('upstream `list-view` declares the toggle the refusal names: `userActions.filter`', () => { + const userActions = SpecListViewSchema.shape.userActions.unwrap(); + expect('filter' in userActions.shape).toBe(true); + }); +}); + +/* ── The TypeScript twin ────────────────────────────────────────────────── */ + +/** + * The `@ts-expect-error` line below IS the assertion: the member is `?: never`, + * so the literal is a compile error. It fails this package's + * `tsc -p tsconfig.test.json` leg as an UNUSED directive if the member is + * deleted (the key would fall back to `BaseSchema`'s index signature) or + * re-typed as a live value. + */ +export const authoredShowFiltersRefused: ObjectGridSchemaType = { + ...GRID, + // @ts-expect-error — `showFilters` is RETIRED on `object-grid` (objectui#11068): a list view's `userActions.filter`. + showFilters: true, +}; + +/** LIT CONTROL: the spellings that stay live are declared, writable members. */ +export const listViewFilterToggleIsDeclared: ListViewSchemaType = { + type: 'list-view', + objectName: 'probe', + columns: ['name'], + userActions: { filter: true }, +}; + +export const objectViewShowFiltersIsDeclared: ObjectViewSchemaType = { + type: 'object-view', + objectName: 'probe', + showFilters: true, +}; + +describe('the TypeScript twin', () => { + it('is compiled by this package’s type-check leg, which is where the bindings above are read', () => { + expect([authoredShowFiltersRefused, listViewFilterToggleIsDeclared, objectViewShowFiltersIsDeclared]).toHaveLength(3); + }); +}); diff --git a/packages/types/src/__tests__/object-view-slot-key-lists.test.ts b/packages/types/src/__tests__/object-view-slot-key-lists.test.ts index ce1768f9e1..fe98f904bf 100644 --- a/packages/types/src/__tests__/object-view-slot-key-lists.test.ts +++ b/packages/types/src/__tests__/object-view-slot-key-lists.test.ts @@ -129,9 +129,9 @@ const FORM_IDENTITY_KEYS = ['type', 'objectName', 'mode'] as const; */ const TABLE_WITHHELD_BY_REASON = { /** - * `ObjectGrid` has no read of it, so nothing could draw it. Four of these are + * `ObjectGrid` has no read of it, so nothing could draw it. Five of these are * retirement tombstones on `ObjectGridSchema` itself since objectui#11068 - * (`bulkSpecActions`, `name`, `placeholder`, `rowSpecActions`). + * (`bulkSpecActions`, `name`, `placeholder`, `rowSpecActions`, `showFilters`). */ unread: ['bulkSpecActions', 'keyboardNavigation', 'name', 'placeholder', 'rowSpecActions', 'showFilters'], /** @@ -157,7 +157,7 @@ const TABLE_WITHHELD_KEYS: readonly string[] = Object.values(TABLE_WITHHELD_BY_R /** * Retirement tombstones `ObjectGridSchema` declares itself (`?: never` on both * faces) that the slot keeps: they type nothing, and they carry the named - * refusal and its guidance onto this face too. The four the grid retired in + * refusal and its guidance onto this face too. The five the grid retired in * objectui#11068 are in the withheld set above instead, refused by the slot's * own message. */ diff --git a/packages/types/src/objectql.ts b/packages/types/src/objectql.ts index e4a65b1f2e..a39e046fb9 100644 --- a/packages/types/src/objectql.ts +++ b/packages/types/src/objectql.ts @@ -991,11 +991,28 @@ export interface ObjectGridSchema extends BaseSchema { showSearch?: boolean; /** - * @deprecated Use filter property instead - * Legacy filters toggle + * RETIRED on this node (objectui#11068, ADR-0049) — the grid has no filter UI. + * + * `ObjectGrid` never read the key, so an authored value drew nothing. The one + * filter surface objectui draws is the `list-view` toolbar's filter builder, + * switched by the list view's `userActions.filter`; honouring the key + * here would have built a second one, and the upstream protocol's + * `object-grid` row does not declare it. The rows this grid fetches are + * narrowed by {@link ObjectGridSchema.filter}. + * + * `?: never`, not a deletion — the convention of `rowSpecActions` below: + * without a member, the key would fall to {@link BaseSchema}'s index + * signature and type-check again. Lockstep with the Zod twin + * (`zod/objectql.zod.ts`, `retirementTombstone()`). An `object-view`'s own + * {@link ObjectViewSchema.showFilters} is a different member, read by + * `ObjectView`, and is unaffected. + * + * @deprecated RETIRED (objectui#11068) — author a `list-view` and switch its + * filter builder with `userActions.filter`; write `filter` to narrow this + * grid's rows. */ - showFilters?: boolean; - + showFilters?: never; + /** * @deprecated Use pagination config instead * Legacy pagination toggle @@ -2133,14 +2150,14 @@ export interface ObjectFormSchema extends BaseSchema { * active named view declares the same member, the named view wins; * - three of the retirement tombstones `ObjectGridSchema` declares (`body`, * `children`, `defaultSort`, each `?: never`). They type nothing: they keep - * the named refusal, and its guidance, on this face too. The four the grid + * the named refusal, and its guidance, on this face too. The five the grid * retired later (`name`, `placeholder`, `rowSpecActions`, - * `bulkSpecActions`, objectui#11068) are withheld instead, below — the - * slot refuses each of them by name either way. + * `bulkSpecActions`, `showFilters`, objectui#11068) are withheld instead, + * below — the slot refuses each of them by name either way. * * ⛔ Every other `ObjectGridSchema` member is WITHHELD, because on the view's - * grid it reached nothing: `ObjectGrid` has no read of it (`showFilters`, - * `keyboardNavigation`, and the four tombstones just named); `ObjectGrid` + * grid it reached nothing: `ObjectGrid` has no read of it + * (`keyboardNavigation`, and the five tombstones just named); `ObjectGrid` * reads it on its own node but the view does not hand it on (`emptyState`, * `description` — honoured by the grid since objectui#11068, and kept off * this slot by that card's ruling, which enforced them without widening it); diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index fa554ca4d6..79b3cee3cc 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -272,6 +272,11 @@ const OBJECT_GRID_NAME_RETIRED = const OBJECT_GRID_PLACEHOLDER_RETIRED = 'RETIRED on `object-grid` (objectui#11068, ADR-0049) — a grid is not an input, and nothing read ' + '`placeholder`. The text the grid shows when it has no rows is `emptyState: { message }`.'; +const OBJECT_GRID_SHOW_FILTERS_RETIRED = + 'RETIRED on `object-grid` (objectui#11068, ADR-0049) — the grid has no filter UI, and nothing read ' + + '`showFilters`, so an authored value drew nothing. The filter builder is the `list-view` toolbar\'s: ' + + 'author a `list-view` and switch the builder with its `userActions.filter`. To narrow the rows this ' + + 'grid fetches, write `filter`.'; /** * objectui#11070 — ONE `.describe()` string for every object-bound arm that declares @@ -337,7 +342,6 @@ export const ObjectGridSchema = BaseSchema.extend({ selectable: z.union([z.boolean(), z.enum(['single', 'multiple'])]).optional(), pageSize: z.number().optional(), showSearch: z.boolean().optional(), - showFilters: z.boolean().optional(), showPagination: z.boolean().optional(), // objectui#5861 — RETIRED under ADR-0049, in lockstep with the `?: never` // twin on the interface. `@objectstack/spec` refuses this key BY NAME on @@ -414,10 +418,18 @@ export const ObjectGridSchema = BaseSchema.extend({ // tombstone rather than a deletion, as for `defaultSort`: `BaseSchema` is // `.passthrough()` (and declares `name` / `placeholder` itself), so without a // member here the key would be KEPT unexamined, not refused. + // + // `showFilters` followed on triage's retriage answer: the grid has no filter + // UI, and the one filter surface objectui draws is the `list-view` toolbar's + // builder (`userActions.filter`), so honouring the key here would have built a + // second one. The upstream `object-grid` row does not declare it either. An + // `object-view`'s or a `list-view`'s own `showFilters` is a different member, + // read by its own renderer, and is untouched. rowSpecActions: retirementTombstone(OBJECT_GRID_ROW_SPEC_ACTIONS_RETIRED), bulkSpecActions: retirementTombstone(OBJECT_GRID_BULK_SPEC_ACTIONS_RETIRED), name: retirementTombstone(OBJECT_GRID_NAME_RETIRED), placeholder: retirementTombstone(OBJECT_GRID_PLACEHOLDER_RETIRED), + showFilters: retirementTombstone(OBJECT_GRID_SHOW_FILTERS_RETIRED), // objectui#11068 — read by `ObjectGrid`, which draws it in place of an empty // table. Mirrored member for member with the interface: three optional // strings, and an unknown member refused rather than kept, so a misspelt @@ -727,9 +739,9 @@ export const ObjectFormSchema = BaseSchema.extend({ * omitted key would be KEPT unexamined, and the slot would accept silently * what the TypeScript face refuses. Three of the tombstones `ObjectGridSchema` * itself declares (`body`, `children`, `defaultSort`) are inherited unchanged; - * the four it retired later (`name`, `placeholder`, `rowSpecActions`, - * `bulkSpecActions`, objectui#11068) are overridden below by this slot's own - * refusal, which names the same remedy. + * the five it retired later (`name`, `placeholder`, `rowSpecActions`, + * `bulkSpecActions`, `showFilters`, objectui#11068) are overridden below by this + * slot's own refusal. */ const tableKeyRefusal = (key: string, why: string) => retirementTombstone(