diff --git a/.changeset/chartconfig-trim-zoom-clickaction.md b/.changeset/chartconfig-trim-zoom-clickaction.md new file mode 100644 index 0000000000..68ea61688d --- /dev/null +++ b/.changeset/chartconfig-trim-zoom-clickaction.md @@ -0,0 +1,38 @@ +--- +'@objectstack/spec': minor +--- + +**BREAKING** `ChartInteraction` drops `zoom` and `clickAction`; `stepSize` / `description` / `height` are delivered (issue #3752) + +The tail of the declared-≠-delivered sweep from #3729. Five `ChartConfig` props +reached the renderer and did nothing; each got the ADR-0078 call — honor it, or +remove it. Three were honored (objectui#2885), two are removed here. + +**Removed — `ChartInteraction.zoom` and `ChartInteraction.clickAction`.** Both +were redundant against something the platform already delivers, which is why +neither had a consumer anywhere in the framework, the console, the showcase, or +the skill corpus: + +- `zoom` had no renderer primitive behind it, and `brush` already narrows a + range. **Migration:** `interaction: { brush: true }`. +- `clickAction` competed with two click owners that *do* work — `drillDown` + (opens the filtered records, which is what a segment click is almost always + for) and, in the react tier, the host's own `onSegmentClick`. A third, silent + owner only invited authors to wire a click that never fired. + **Migration:** `drillDown`, or handle the click in React. + +`ChartInteraction` is now `{ tooltips, brush }` — both honored. This follows the +#1475 precedent: trim what cannot be cleanly delivered, implement the rest, and +leave nothing declared-but-inert in between. + +**Delivered — `ChartAxis.stepSize`, `ChartConfig.description`, `ChartConfig.height`** +(objectui#2885). `description` and `height` join ``'s published +`dataProps` now that they do something; `stepSize` rides along inside +`xAxis`/`yAxis`. Their schema descriptions say what they actually do rather than +restating their names. + +Breaking, but shipped as `minor` per the launch-window convention (see +`scripts/check-changeset-no-major.mjs`). Off-spec `zoom`/`clickAction` keys are +stripped by Zod rather than rejected, so no stored metadata fails to parse — the +break is at the TypeScript type level for anyone constructing a +`ChartInteraction` in code. diff --git a/content/docs/references/ui/chart.mdx b/content/docs/references/ui/chart.mdx index e17a0c02cd..448ed8bc54 100644 --- a/content/docs/references/ui/chart.mdx +++ b/content/docs/references/ui/chart.mdx @@ -100,16 +100,16 @@ Inline aggregation for an object-bound chart | **type** | `Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'treemap' \| 'sankey' \| 'gauge' \| 'solid-gauge' \| 'metric' \| 'kpi' \| 'bullet' \| 'radar' \| 'table' \| 'pivot'>` | ✅ | | | **title** | `string` | optional | Chart title | | **subtitle** | `string` | optional | Chart subtitle | -| **description** | `string` | optional | Accessibility description | +| **description** | `string` | optional | Accessibility description — announced to screen readers as the chart’s label | | **xAxis** | `{ field: string; title?: string; format?: string; min?: number; … }` | optional | X-Axis configuration | | **yAxis** | `{ field: string; title?: string; format?: string; min?: number; … }[]` | optional | Y-Axis configuration (support dual axis) | | **series** | `{ name: string; label?: string; type?: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'treemap' \| 'sankey' \| 'gauge' \| 'solid-gauge' \| 'metric' \| 'kpi' \| 'bullet' \| 'radar' \| 'table' \| 'pivot'>; color?: string; … }[]` | optional | Defined series configuration | | **colors** | `string[] \| Record` | optional | Color palette (string[]) or value→color map (`{ value: color }`) | -| **height** | `number` | optional | Fixed height in pixels | +| **height** | `number` | optional | Fixed plot height in pixels (overrides the container default) | | **showLegend** | `boolean` | ✅ | Display legend | | **showDataLabels** | `boolean` | ✅ | Display data labels | | **annotations** | `{ type: Enum<'line' \| 'region'>; axis: Enum<'x' \| 'y'>; value: number \| string; endValue?: number \| string; … }[]` | optional | Reference lines/bands drawn over the plot: `{ type: "line" \| "region", axis: "x" \| "y", value, endValue?, color?, label?, style? }` | -| **interaction** | `{ tooltips: boolean; zoom: boolean; brush: boolean; clickAction?: string }` | optional | Interaction toggles: `{ tooltips?, brush?, zoom?, clickAction? }` | +| **interaction** | `{ tooltips: boolean; brush: boolean }` | optional | Interaction toggles: `{ tooltips?, brush? }` | | **aria** | `{ ariaLabel?: string; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes | @@ -151,9 +151,7 @@ Type: `string` | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **tooltips** | `boolean` | ✅ | Show the hover tooltip | -| **zoom** | `boolean` | ✅ | Pan/zoom the plot — NOT implemented by the default renderer; use `brush` | | **brush** | `boolean` | ✅ | Show the range selector under the plot | -| **clickAction** | `string` | optional | Action ID to trigger on click | --- diff --git a/content/docs/references/ui/report.mdx b/content/docs/references/ui/report.mdx index ba7d583b57..51e78ac3a5 100644 --- a/content/docs/references/ui/report.mdx +++ b/content/docs/references/ui/report.mdx @@ -82,16 +82,16 @@ const result = JoinedReportBlock.parse(data); | **type** | `Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'treemap' \| 'sankey' \| 'gauge' \| 'solid-gauge' \| 'metric' \| 'kpi' \| 'bullet' \| 'radar' \| 'table' \| 'pivot'>` | ✅ | | | **title** | `string` | optional | Chart title | | **subtitle** | `string` | optional | Chart subtitle | -| **description** | `string` | optional | Accessibility description | +| **description** | `string` | optional | Accessibility description — announced to screen readers as the chart’s label | | **xAxis** | `string` | ✅ | Dataset dimension name for the X-axis (bound-dataset dimension, not a raw field) | | **yAxis** | `string` | ✅ | Dataset measure name for the Y-axis (bound-dataset measure, not a raw field) | | **series** | `{ name: string; label?: string; type?: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'treemap' \| 'sankey' \| 'gauge' \| 'solid-gauge' \| 'metric' \| 'kpi' \| 'bullet' \| 'radar' \| 'table' \| 'pivot'>; color?: string; … }[]` | optional | Defined series configuration | | **colors** | `string[] \| Record` | optional | Color palette (string[]) or value→color map (`{ value: color }`) | -| **height** | `number` | optional | Fixed height in pixels | +| **height** | `number` | optional | Fixed plot height in pixels (overrides the container default) | | **showLegend** | `boolean` | ✅ | Display legend | | **showDataLabels** | `boolean` | ✅ | Display data labels | | **annotations** | `{ type: Enum<'line' \| 'region'>; axis: Enum<'x' \| 'y'>; value: number \| string; endValue?: number \| string; … }[]` | optional | Reference lines/bands drawn over the plot: `{ type: "line" \| "region", axis: "x" \| "y", value, endValue?, color?, label?, style? }` | -| **interaction** | `{ tooltips: boolean; zoom: boolean; brush: boolean; clickAction?: string }` | optional | Interaction toggles: `{ tooltips?, brush?, zoom?, clickAction? }` | +| **interaction** | `{ tooltips: boolean; brush: boolean }` | optional | Interaction toggles: `{ tooltips?, brush? }` | | **aria** | `{ ariaLabel?: string; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes | diff --git a/packages/spec/src/ui/chart.zod.ts b/packages/spec/src/ui/chart.zod.ts index ff623a7818..5b1daa9063 100644 --- a/packages/spec/src/ui/chart.zod.ts +++ b/packages/spec/src/ui/chart.zod.ts @@ -72,9 +72,11 @@ export const ChartTypeSchema = lazySchema(() => z.enum([ // (`ChartSeries.stack` — series sharing a group id stack, otherwise they // group). One `bar` family plus a series-level stack group expresses all three // without multiplying the taxonomy. The renderer honors it (objectui#2880); -// before that it did not, which is why they once sat in the list above. (`metric`/`kpi` are kept as honest single-value -// synonyms; `gauge`/`solid-gauge`/`bullet` render a value today and gain a dial -// when a gauge renderer lands.) +// before that it did not, which is why they once sat in the list above. +// +// `metric`/`kpi` are kept as honest single-value synonyms; `gauge`/ +// `solid-gauge`/`bullet` render a value today and gain a dial when a gauge +// renderer lands. export type ChartType = z.infer; @@ -163,18 +165,23 @@ export const ChartAnnotationSchema = lazySchema(() => z.object({ /** * Chart Interaction Schema + * + * Both toggles are honored by the renderer. Two former members were removed in + * #3752 rather than left declared-but-inert (ADR-0078; the #1475 trim-vs- + * implement call), because each was redundant against something the platform + * already delivers: + * + * * `zoom` — no renderer had a zoom primitive behind it, and `brush` already + * narrows a range. Migration: `brush: true`. + * * `clickAction` — a chart segment click already has two owners that DO + * work: `drillDown` (opens the filtered records, which is what a segment + * click is almost always for) and, in the react tier, the host's own + * `onSegmentClick`. A third, silent one only invited authors to wire a + * click that never fired. Migration: `drillDown`, or handle it in React. */ export const ChartInteractionSchema = lazySchema(() => z.object({ tooltips: z.boolean().default(true).describe('Show the hover tooltip'), - /** - * ⚠️ Declared, not delivered. The default Recharts renderer has no zoom - * primitive, so this draws nothing today — `brush` is the shipped way to - * narrow a range. Kept in the schema because an opt-in renderer can honor it; - * tracked with the rest of the chart-contract work in framework#3729. - */ - zoom: z.boolean().default(false).describe('Pan/zoom the plot — NOT implemented by the default renderer; use `brush`'), brush: z.boolean().default(false).describe('Show the range selector under the plot'), - clickAction: z.string().optional().describe('Action ID to trigger on click'), })); /** @@ -188,7 +195,7 @@ export const ChartConfigSchema = lazySchema(() => z.object({ /** Titles */ title: I18nLabelSchema.optional().describe('Chart title'), subtitle: I18nLabelSchema.optional().describe('Chart subtitle'), - description: I18nLabelSchema.optional().describe('Accessibility description'), + description: I18nLabelSchema.optional().describe('Accessibility description — announced to screen readers as the chart’s label'), /** Axes Mapping */ xAxis: ChartAxisSchema.optional().describe('X-Axis configuration'), @@ -206,7 +213,7 @@ export const ChartConfigSchema = lazySchema(() => z.object({ z.array(z.string()), z.record(z.string(), z.string()), ]).optional().describe('Color palette (string[]) or value→color map ({ value: color })'), - height: z.number().optional().describe('Fixed height in pixels'), + height: z.number().optional().describe('Fixed plot height in pixels (overrides the container default)'), /** Components */ showLegend: z.boolean().default(true).describe('Display legend'), @@ -218,7 +225,7 @@ export const ChartConfigSchema = lazySchema(() => z.object({ /** Interactions */ interaction: ChartInteractionSchema.optional() - .describe('Interaction toggles: { tooltips?, brush?, zoom?, clickAction? }'), + .describe('Interaction toggles: { tooltips?, brush? }'), /** ARIA accessibility attributes */ aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'), diff --git a/packages/spec/src/ui/react-blocks.ts b/packages/spec/src/ui/react-blocks.ts index 387354b36b..9ea86fe97d 100644 --- a/packages/spec/src/ui/react-blocks.ts +++ b/packages/spec/src/ui/react-blocks.ts @@ -116,7 +116,7 @@ export const REACT_BLOCKS: ReactBlockDef[] = [ // react-page wrapper parks an author `type` beside it and the renderer // reads it back. It is published here as the spec spells it; the internal // `chartType` spelling is no longer part of the author contract. - dataProps: ['type', 'title', 'subtitle', 'xAxis', 'yAxis', 'series', 'colors', 'showLegend', 'showDataLabels', 'annotations', 'interaction'], + dataProps: ['type', 'title', 'subtitle', 'description', 'height', 'xAxis', 'yAxis', 'series', 'colors', 'showLegend', 'showDataLabels', 'annotations', 'interaction'], interactions: [ OBJECT_NAME, { name: 'filter', type: 'FilterArray', kind: 'controlled', description: 'ObjectQL filter scoping the data; drive from React state.' }, diff --git a/skills/objectstack-ui/contracts/react-blocks.contract.json b/skills/objectstack-ui/contracts/react-blocks.contract.json index 9561b30291..18ec1ff549 100644 --- a/skills/objectstack-ui/contracts/react-blocks.contract.json +++ b/skills/objectstack-ui/contracts/react-blocks.contract.json @@ -341,6 +341,20 @@ "required": false, "description": "Chart subtitle" }, + { + "name": "description", + "type": "string", + "kind": "data", + "required": false, + "description": "Accessibility description — announced to screen readers as the chart’s label" + }, + { + "name": "height", + "type": "number", + "kind": "data", + "required": false, + "description": "Fixed plot height in pixels (overrides the container default)" + }, { "name": "xAxis", "type": "object", @@ -395,7 +409,7 @@ "type": "object", "kind": "data", "required": false, - "description": "Interaction toggles: { tooltips?, brush?, zoom?, clickAction? }" + "description": "Interaction toggles: { tooltips?, brush? }" } ] }, diff --git a/skills/objectstack-ui/references/react-blocks.md b/skills/objectstack-ui/references/react-blocks.md index 1afbaa9434..f4b7a9ab7f 100644 --- a/skills/objectstack-ui/references/react-blocks.md +++ b/skills/objectstack-ui/references/react-blocks.md @@ -76,6 +76,8 @@ Chart over an object’s aggregated data. Bind objectName + aggregate; the axes | `type` | `'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'treemap' \| 'sankey' \| 'gauge' \| 'solid-gauge' \| 'metric' \| 'kpi' \| 'bullet' \| 'radar' \| 'table' \| 'pivot'` | data | ✓ | | | `title` | `string` | data | | Chart title | | `subtitle` | `string` | data | | Chart subtitle | +| `description` | `string` | data | | Accessibility description — announced to screen readers as the chart’s label | +| `height` | `number` | data | | Fixed plot height in pixels (overrides the container default) | | `xAxis` | `object` | data | | X-Axis configuration | | `yAxis` | `object[]` | data | | Y-Axis configuration (support dual axis) | | `series` | `object[]` | data | | Defined series configuration | @@ -83,7 +85,7 @@ Chart over an object’s aggregated data. Bind objectName + aggregate; the axes | `showLegend` | `boolean` | data | | Display legend | | `showDataLabels` | `boolean` | data | | Display data labels | | `annotations` | `object[]` | data | | Reference lines/bands drawn over the plot: { type: "line" \| "region", axis: "x" \| "y", value, endValue?, color?, label?, style? } | -| `interaction` | `object` | data | | Interaction toggles: { tooltips?, brush?, zoom?, clickAction? } | +| `interaction` | `object` | data | | Interaction toggles: { tooltips?, brush? } | ## `` — `record:details`