Skip to content

Commit 50dc214

Browse files
claude[bot]claude
andauthored
docs(spec): globalFilters[].field describes where the name resolves — the dataset object, not its dimensions (#16968)
* docs(spec): say where a dashboard global filter's field resolves `globalFilters[].field` described itself only as "Field name to filter on", which left the generated reference page silent on the one thing authors get wrong: the name resolves against the object behind each bound widget's dataset (`dataset.object`), not against that dataset's declared `dimensions`. The rewritten `.describe()` 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). The mirrored TSDoc carries the second half the one-line form cannot: the separation is a statement about the authorable surface only, NOT a claim that an object field can never serve as a dimension — the analytics query API does accept one ad hoc without the dataset declaring it. Wording aligned with the hand-written page's `## Global Filters` section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x * docs(spec): regenerate the dashboard reference page, add the changeset `content/docs/references/ui/dashboard.mdx` is a build product of the spec's docs build — its line 6 says so — so the new `globalFilters[].field` description reaches it by regeneration, never by hand. Two rows carry the sentence because two sections render the same `GlobalFilterSchema`: `### Nested Shape: Dashboard.globalFilters[number]` and the top-level `## GlobalFilter` → `### Properties`. No other reference page moved: the identically-spelled `field` describe on the list-view schema is a different surface and was deliberately left alone. Changeset is `patch` on `@objectstack/spec`: the string is a published datum, not a comment — it is the `description` of the property in the shipped JSON Schema and the runtime `.description` on the Zod schema in `dist`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x * docs(spec): disarm the `object` adjacency in the `field` docblock Two adjacent docblocks in `GlobalFilterSchema` each say "the object the field lives on" about a different resolution: `field` resolves a COLUMN against the bound widget's `dataset.object`, while the sibling `object` key resolves a translation-bundle KEY. They do not collide, but a reader who meets both may infer that setting `object` re-points the filter — the same reader-facing shape this docblock was just rewritten to fix. One clause in the `field` TSDoc closes it at the point of confusion. The `object` docblock is untouched, and the `.describe()` string is byte-unchanged, so the generated reference page and the changeset need no edit. The citation is anchored by SYMBOL, not by line: the exported rule-id constant `DASHBOARD_FILTER_FIELD_UNKNOWN`, the enclosing `validateWidgetBindings`, and a verbatim snippet measured to grep to exactly one hit in that file. A `path:NNN` prose anchor would have started rotting on the next edit to the file it names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8c1515e commit 50dc214

3 files changed

Lines changed: 40 additions & 4 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`Dashboard.globalFilters[].field` now describes WHERE the name resolves, instead of only that it is a field name.
6+
7+
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.
8+
9+
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.
10+
11+
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.
12+
13+
**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.
14+
15+
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.

content/docs/references/ui/dashboard.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const result = DashboardSchema.parse(data);
9797
| Property | Type | Required | Description |
9898
| :--- | :--- | :--- | :--- |
9999
| **name** | `string` | optional | Stable filter name (variable key); defaults to field |
100-
| **field** | `string` || Field name to filter on |
100+
| **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) |
101101
| **object** | `string` | optional | Object whose `fields.<object>.<field>` translation-bundle entry resolves this filter's field label and option labels |
102102
| **label** | `string \| Record<string, string>` | optional | Display label for the filter |
103103
| **type** | `Enum<'text' \| 'select' \| 'date' \| 'number' \| 'lookup'>` | optional | Filter input type |
@@ -275,7 +275,7 @@ Widget configuration — declared query keys + open renderer extras
275275
| Property | Type | Required | Description |
276276
| :--- | :--- | :--- | :--- |
277277
| **name** | `string` | optional | Stable filter name (variable key); defaults to field |
278-
| **field** | `string` || Field name to filter on |
278+
| **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) |
279279
| **object** | `string` | optional | Object whose `fields.<object>.<field>` translation-bundle entry resolves this filter's field label and option labels |
280280
| **label** | `string \| Record<string, string>` | optional | Display label for the filter |
281281
| **type** | `Enum<'text' \| 'select' \| 'date' \| 'number' \| 'lookup'>` | optional | Filter input type |

packages/spec/src/ui/dashboard.zod.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,8 +800,29 @@ export const GlobalFilterSchema = lazySchema(() => strictObject({
800800
*/
801801
name: z.string().optional().describe('Stable filter name (variable key); defaults to field'),
802802

803-
/** Field name to filter on */
804-
field: z.string().describe('Field name to filter on'),
803+
/**
804+
* Field name to filter on — at the authoring layer it resolves against the
805+
* object behind each bound widget's dataset (`dataset.object`), not against
806+
* that dataset's declared `dimensions`; enforced by the lint rule
807+
* `dashboard-filter-field-unknown` (severity error).
808+
*
809+
* The sibling `object` key does not move this: it names the object a
810+
* translator's bundle entry is keyed by and is read for LABEL resolution
811+
* only. `dashboard-filter-field-unknown` (exported as
812+
* `DASHBOARD_FILTER_FIELD_UNKNOWN`) resolves against the widget's own
813+
* dataset — `validateWidgetBindings` seeds its base with
814+
* `const datasetObject = typeof dataset.object === 'string'` — and never
815+
* reads a filter-level `object`.
816+
*
817+
* `dimensions` is a separate namespace, the one `widgets[].dimensions[]`
818+
* selects from BY NAME (`widget-dimension-unknown`, also severity error).
819+
* That separation is a statement about the AUTHORABLE SURFACE only — it is
820+
* NOT a claim that an object field can never serve as a dimension: the
821+
* analytics query API does accept an object's own field as an ad-hoc
822+
* dimension without the dataset declaring it, and `widget-dimension-unknown`
823+
* is what holds that line for authored dashboards.
824+
*/
825+
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)'),
805826

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

0 commit comments

Comments
 (0)