Skip to content

Commit 399231d

Browse files
committed
feat(spec): rename the view filter any-of group key from any to or
The maintainer ruled the stored shape stands and its key is `or`, matching the and/or wording of Notion, monday.com and Dataverse filters; a nested level added later would pair with `and`. - `{ any: [rule, ...] }` becomes `{ or: [rule, ...] }` in the schema, the row-id strip, describes, TSDoc and examples, the lint readers, the view reference diagnostics, tests, changeset and liveness note. - Symbols follow: ViewFilterOrGroupSchema, ViewFilterOrGroup(Parsed), isViewFilterOrGroup, VIEW_FILTER_OR_GROUP_KEY. Unreleased, so no aliases; the manifest and authorable-surface baselines rename their branch-added lines in place. - `or` leaves the rule's OR-spelling guidance (it is the real key now); `any` joins `$or`, `anyOf`, `logic` and `conditions` there. A rule carrying `or` inside a group keeps the nested-group refusal. - Test files renamed to *-or-group; references regenerated. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
1 parent 9d54c7f commit 399231d

28 files changed

Lines changed: 245 additions & 239 deletions
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,30 @@
44
'@objectstack/metadata-protocol': minor
55
---
66

7-
feat(spec): a stored view filter list can hold an any-of group, `{ any: [rule, ...] }`
7+
feat(spec): a stored view filter list can hold an any-of group, `{ or: [rule, ...] }`
88

99
Clause-②: yes (widening)
1010
Metadata-protocol: added
1111

12-
A view filter list (`ListView.filter`, a view tab's `filter`, `Page.interfaceConfig.filterBy`, an element data source's `filter`, the related-list and picker filters and the component `filter` doors) was an AND list of flat `{ field, operator, value }` rules, so a view whose natural filter is an any-of ("open, or completed in the last 7 days") could not be stored. The list now takes one more element kind beside the rule: `{ any: [rule, ...] }`, a non-empty array of flat rules of which ANY may match. The list still ANDs its elements.
12+
A view filter list (`ListView.filter`, a view tab's `filter`, `Page.interfaceConfig.filterBy`, an element data source's `filter`, the related-list and picker filters and the component `filter` doors) was an AND list of flat `{ field, operator, value }` rules, so a view whose natural filter is an any-of ("open, or completed in the last 7 days") could not be stored. The list now takes one more element kind beside the rule: `{ or: [rule, ...] }`, a non-empty array of flat rules of which ANY may match. The list still ANDs its elements.
1313

1414
```ts
1515
filter: [
16-
{ any: [
16+
{ or: [
1717
{ field: 'status', operator: 'equals', value: 'open' },
1818
{ field: 'completed_at', operator: 'after', value: '{7_days_ago}' },
1919
] },
2020
{ field: 'owner', operator: 'equals', value: '{current_user_id}' },
2121
]
2222
```
2323

24-
- New exports from `@objectstack/spec/ui`: `ViewFilterAnyGroupSchema`, `ViewFilterElementSchema` (rule or group — what every carrier holds now), their `ViewFilterAnyGroup` / `ViewFilterElement` types and `...Parsed` twins, and `isViewFilterAnyGroup(value)`, the one test that tells the two element kinds apart.
24+
- New exports from `@objectstack/spec/ui`: `ViewFilterOrGroupSchema`, `ViewFilterElementSchema` (rule or group — what every carrier holds now), their `ViewFilterOrGroup` / `ViewFilterElement` types and `...Parsed` twins, and `isViewFilterOrGroup(value)`, the one test that tells the two element kinds apart.
2525
- A group's members are `ViewFilterRuleSchema` itself, so the operator vocabulary, the legacy-alias fold, the operator/value-shape coupling and the `icontains` comparand check apply inside a group exactly as at the top level.
26-
- Refused at parse: an empty group (`too_small` at `filter.N.any`), a group inside a group (one level only; `unrecognized_keys` naming `any` on the inner element), a member that is not a rule, and a rule key written beside `any`. `$or`, `or`, `anyOf`, `logic` and `conditions` written on a rule are answered with the group shape.
26+
- Refused at parse: an empty group (`too_small` at `filter.N.or`), a group inside a group (one level only; `unrecognized_keys` naming `or` on the inner element), a member that is not a rule, and a rule key written beside `or`. `$or`, `any`, `anyOf`, `logic` and `conditions` written on a rule are answered with the group shape. The key is `or` by the maintainer's ruling: it matches the and/or wording of Notion, monday.com and Dataverse filters, and a nested level added later would pair with `and`.
2727
- Every filter stored before this change parses exactly as it did: the same output for an accepted rule, and the same issues (code, path, message) for a refused one — the element union reports only the branch the element claims.
2828
- The wire door strips the console's row `id` inside a group as on a plain rule.
29-
- `ViewFilterRule` (the type) is unchanged and still means one flat rule. A reader typed against a carrier's element now sees `ViewFilterElement`, and must step into a group (`isViewFilterAnyGroup`) rather than read `field` off every element.
29+
- `ViewFilterRule` (the type) is unchanged and still means one flat rule. A reader typed against a carrier's element now sees `ViewFilterElement`, and must step into a group (`isViewFilterOrGroup`) rather than read `field` off every element.
3030
- `solution-blueprint` view `filters` deliberately stay flat rules: the model writes them through a strict mirror that cannot carry a union.
3131
- `[PLANNED — not yet rendered]`: no console applies the group yet. The list query build reads flat rules only, so until it reads this element a view that stores a group is refused when it loads (the server answers the passed-through group with a malformed-filter error and the list shows no rows); it fails closed, and the group's describe says so.
3232

33-
Readers updated: `@objectstack/lint`'s filter field-key walk (`filter-walk.ts`, used by the list-view, dataset and widget field-reference rules), the preset-comparand rule, and the page field-binding rule (`filterBy` and a related list's `filter`) step into a group, so a field inside one is checked and `any` is never reported as a field. `@objectstack/metadata-protocol`'s `computeViewReferenceDiagnostics` checks the fields inside a group in a preset tab's filter.
33+
Readers updated: `@objectstack/lint`'s filter field-key walk (`filter-walk.ts`, used by the list-view, dataset and widget field-reference rules), the preset-comparand rule, and the page field-binding rule (`filterBy` and a related list's `filter`) step into a group, so a field inside one is checked and `or` is never reported as a field. `@objectstack/metadata-protocol`'s `computeViewReferenceDiagnostics` checks the fields inside a group in a preset tab's filter.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,7 +1856,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
18561856
| **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | |
18571857
| **data** | `{ provider: 'object'; object: string } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record<string, any> }` | optional | Data source configuration (defaults to "object" provider) |
18581858
| **columns** | `string[] \| { field: string; label?: string \| Record<string, string>; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). |
1859-
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { any: object[] })[]` | optional | Filter criteria (JSON Rules) |
1859+
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { or: object[] })[]` | optional | Filter criteria (JSON Rules) |
18601860
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | |
18611861
| **searchableFields** | `string[]` | optional | Fields enabled for search |
18621862
| **filterableFields** | `never` | optional | [REMOVED] `view.list.filterableFields` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — the key was declared as a shorthand for `userFilters.fields` (the quick-filter bar), but the one renderer that read it used it as a whitelist of the fields the Filter panel offers instead, so one key carried two meanings and drew no quick filter. Delete the key; no per-view Filter-panel whitelist replaces it. For a curated end-user filter bar, declare `userFilters: { element: 'dropdown', fields: [{ field: '<field_name>' }] }`. The Filter panel offers every field the user may read: field read security decides, and a field the object definition marks `hidden: true` is not offered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -1935,7 +1935,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
19351935
| **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | |
19361936
| **data** | `{ provider: 'object'; object: string } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record<string, any> }` | optional | Data source configuration (defaults to "object" provider) |
19371937
| **columns** | `string[] \| { field: string; label?: string \| Record<string, string>; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). |
1938-
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { any: object[] })[]` | optional | Filter criteria (JSON Rules) |
1938+
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { or: object[] })[]` | optional | Filter criteria (JSON Rules) |
19391939
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | |
19401940
| **searchableFields** | `string[]` | optional | Fields enabled for search |
19411941
| **filterableFields** | `never` | optional | [REMOVED] `view.list.filterableFields` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — the key was declared as a shorthand for `userFilters.fields` (the quick-filter bar), but the one renderer that read it used it as a whitelist of the fields the Filter panel offers instead, so one key carried two meanings and drew no quick filter. Delete the key; no per-view Filter-panel whitelist replaces it. For a curated end-user filter bar, declare `userFilters: { element: 'dropdown', fields: [{ field: '<field_name>' }] }`. The Filter panel offers every field the user may read: field read security decides, and a field the object definition marks `hidden: true` is not offered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ const result = ApiMethod.parse(data);
375375
| **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | |
376376
| **data** | `{ provider: 'object'; object: string } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record<string, any> }` | optional | Data source configuration (defaults to "object" provider) |
377377
| **columns** | `string[] \| { field: string; label?: string \| Record<string, string>; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). |
378-
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { any: object[] })[]` | optional | Filter criteria (JSON Rules) |
378+
| **filter** | `({ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] } \| { or: object[] })[]` | optional | Filter criteria (JSON Rules) |
379379
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | |
380380
| **searchableFields** | `string[]` | optional | Fields enabled for search |
381381
| **filterableFields** | `never` | optional | [REMOVED] `view.list.filterableFields` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — the key was declared as a shorthand for `userFilters.fields` (the quick-filter bar), but the one renderer that read it used it as a whitelist of the fields the Filter panel offers instead, so one key carried two meanings and drew no quick filter. Delete the key; no per-view Filter-panel whitelist replaces it. For a curated end-user filter bar, declare `userFilters: { element: 'dropdown', fields: [{ field: '<field_name>' }] }`. The Filter panel offers every field the user may read: field read security decides, and a field the object definition marks `hidden: true` is not offered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |

‎content/docs/references/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay
392392
| [`report.zod.ts`](/docs/references/ui/report) | `JoinedReportBlock`, `Report`, `ReportChart`, `ReportSort`, `ReportType` |
393393
| [`responsive.zod.ts`](/docs/references/ui/responsive) | `ResponsiveStyles`, `StyleMap` |
394394
| [`sharing.zod.ts`](/docs/references/ui/sharing) | `SharingConfig` |
395-
| [`view.zod.ts`](/docs/references/ui/view) | `AddRecordConfig`, `AppearanceConfig`, `CalendarConfig`, `ColumnPrefix`, `ColumnSummary`, `ColumnSummaryConfig`, `EmptyState`, `FormButtonConfig`, `FormField`, `FormSection`, `FormSelectOption`, `FormView`, `GalleryConfig`, `GanttConfig`, `GanttQuickFilter`, `GroupingConfig`, `GroupingField`, `HttpMethodSubset`, `HttpRequest`, `KanbanConfig`, `ListChartConfig`, `ListColumn`, `ListMapConfig`, `ListView`, `NavigationConfig`, `NavigationMode`, `ObjectListView`, `ObjectUserFilters`, `PaginationConfig`, `RowColorConfig`, `RowHeight`, `SelectionConfig`, `TimelineConfig`, `TreeConfig`, `UserActionsConfig`, `UserFilterField`, `UserFilters`, `View`, `ViewData`, `ViewFilterAnyGroup`, `ViewFilterElement`, `ViewFilterRule`, `ViewItem`, `ViewItemName`, `ViewItemWire`, `ViewKind`, `ViewScope`, `ViewSharing`, `ViewTab`, `VisualizationType` |
395+
| [`view.zod.ts`](/docs/references/ui/view) | `AddRecordConfig`, `AppearanceConfig`, `CalendarConfig`, `ColumnPrefix`, `ColumnSummary`, `ColumnSummaryConfig`, `EmptyState`, `FormButtonConfig`, `FormField`, `FormSection`, `FormSelectOption`, `FormView`, `GalleryConfig`, `GanttConfig`, `GanttQuickFilter`, `GroupingConfig`, `GroupingField`, `HttpMethodSubset`, `HttpRequest`, `KanbanConfig`, `ListChartConfig`, `ListColumn`, `ListMapConfig`, `ListView`, `NavigationConfig`, `NavigationMode`, `ObjectListView`, `ObjectUserFilters`, `PaginationConfig`, `RowColorConfig`, `RowHeight`, `SelectionConfig`, `TimelineConfig`, `TreeConfig`, `UserActionsConfig`, `UserFilterField`, `UserFilters`, `View`, `ViewData`, `ViewFilterElement`, `ViewFilterOrGroup`, `ViewFilterRule`, `ViewItem`, `ViewItemName`, `ViewItemWire`, `ViewKind`, `ViewScope`, `ViewSharing`, `ViewTab`, `VisualizationType` |
396396

397397
---
398398

0 commit comments

Comments
 (0)