Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/dashboard-global-filter-field-resolution-target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@objectstack/spec": patch
---

`Dashboard.globalFilters[].field` now describes WHERE the name resolves, instead of only that it is a field name.

The key's `.describe()` read `Field name to filter on` — true, but silent on the one thing authors get wrong. A dashboard global filter's `field` resolves against the object behind each bound widget's dataset (`dataset.object`), never against that dataset's declared `dimensions`; `widgets[].dimensions[]` selects from that second, separate namespace by name. Readers who assumed the two were the same namespace concluded a filter needs a matching dataset dimension, which is not so.

The rewritten description states the resolution target, scopes the claim to the authoring layer, and names the rule that already enforces it — `dashboard-filter-field-unknown`, `severity: 'error'` in `@objectstack/lint`'s widget-binding validator. It is a statement about a rule that already fires, not a suggestion.

The mirrored TSDoc carries the half a one-line description cannot: the separation holds for the **authorable surface** only. It is NOT a claim that an object field can never serve as a dimension — the analytics query API does accept an object's own field as an ad-hoc dimension without the dataset declaring it, and `widget-dimension-unknown` (also `severity: 'error'`) is what holds that line for authored dashboards.

**What moves for consumers.** The string is a published datum, not a comment: it is the `description` of the `field` property in the shipped JSON Schema (`json-schema/ui/GlobalFilter.json`, `json-schema/ui/Dashboard.json`, `json-schema/objectstack.json`) and the runtime `.description` on the Zod schema in `dist`, so anything that renders schema descriptions — editor hovers, generated reference pages, prompt builders — shows the new sentence. No accept set moves: the key stays `z.string()`, nothing that parses today stops parsing, and no validation behaviour changes.

The same wording already stands on the hand-written page (`content/docs/ui/dashboards.mdx` → **Where a Filter's `field` Resolves**); the generated reference page now agrees with it rather than trailing it.
4 changes: 2 additions & 2 deletions content/docs/references/ui/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const result = DashboardSchema.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | optional | Stable filter name (variable key); defaults to field |
| **field** | `string` | ✅ | Field name to filter on |
| **field** | `string` | ✅ | Field name to filter on — at the authoring layer it resolves against the object behind each bound widget's dataset (`dataset.object`), not against that dataset's declared `dimensions`; enforced by the lint rule `dashboard-filter-field-unknown` (severity error) |
| **object** | `string` | optional | Object whose `fields.<object>.<field>` translation-bundle entry resolves this filter's field label and option labels |
| **label** | `string \| Record<string, string>` | optional | Display label for the filter |
| **type** | `Enum<'text' \| 'select' \| 'date' \| 'number' \| 'lookup'>` | optional | Filter input type |
Expand Down Expand Up @@ -275,7 +275,7 @@ Widget configuration — declared query keys + open renderer extras
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | optional | Stable filter name (variable key); defaults to field |
| **field** | `string` | ✅ | Field name to filter on |
| **field** | `string` | ✅ | Field name to filter on — at the authoring layer it resolves against the object behind each bound widget's dataset (`dataset.object`), not against that dataset's declared `dimensions`; enforced by the lint rule `dashboard-filter-field-unknown` (severity error) |
| **object** | `string` | optional | Object whose `fields.<object>.<field>` translation-bundle entry resolves this filter's field label and option labels |
| **label** | `string \| Record<string, string>` | optional | Display label for the filter |
| **type** | `Enum<'text' \| 'select' \| 'date' \| 'number' \| 'lookup'>` | optional | Filter input type |
Expand Down
25 changes: 23 additions & 2 deletions packages/spec/src/ui/dashboard.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,29 @@ export const GlobalFilterSchema = lazySchema(() => strictObject({
*/
name: z.string().optional().describe('Stable filter name (variable key); defaults to field'),

/** Field name to filter on */
field: z.string().describe('Field name to filter on'),
/**
* Field name to filter on — at the authoring layer it resolves against the
* object behind each bound widget's dataset (`dataset.object`), not against
* that dataset's declared `dimensions`; enforced by the lint rule
* `dashboard-filter-field-unknown` (severity error).
*
* The sibling `object` key does not move this: it names the object a
* translator's bundle entry is keyed by and is read for LABEL resolution
* only. `dashboard-filter-field-unknown` (exported as
* `DASHBOARD_FILTER_FIELD_UNKNOWN`) resolves against the widget's own
* dataset — `validateWidgetBindings` seeds its base with
* `const datasetObject = typeof dataset.object === 'string'` — and never
* reads a filter-level `object`.
*
* `dimensions` is a separate namespace, the one `widgets[].dimensions[]`
* selects from BY NAME (`widget-dimension-unknown`, also severity error).
* That separation is a statement about the AUTHORABLE SURFACE only — it is
* NOT a claim that an object field can never serve as a dimension: the
* analytics query API does accept an object's own field as an ad-hoc
* dimension without the dataset declaring it, and `widget-dimension-unknown`
* is what holds that line for authored dashboards.
*/
field: z.string().describe('Field name to filter on — at the authoring layer it resolves against the object behind each bound widget\'s dataset (`dataset.object`), not against that dataset\'s declared `dimensions`; enforced by the lint rule `dashboard-filter-field-unknown` (severity error)'),

/**
* Source object for i18n label resolution (#7804): when set, this filter's
Expand Down
Loading