Skip to content

Commit 221df98

Browse files
committed
chore(spec): regenerate the migration registry, api-surface, export-origins and the dashboard reference
Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
1 parent c1d4f3d commit 221df98

4 files changed

Lines changed: 88 additions & 8 deletions

File tree

‎content/docs/references/ui/dashboard.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const result = DashboardSchema.parse(data);
7777
| **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) |
7878
| **dataset** | `string` | ✅ | Dataset name to bind (ADR-0021) |
7979
| **dimensions** | `string[]` | optional | Dimension names — X/group/split |
80-
| **values** | `string[]` | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
80+
| **values** | `string[]` | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family and on the single-series pie/donut/funnel/treemap/sankey; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
8181
| **layout** | `{ x: number; y: number; w: number; h: number }` | optional | Grid layout position (auto-flowed when omitted) |
8282
| **options** | `{ dateGranularity?: Enum<'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; sortBy?: string; sortOrder?: Enum<'asc' \| 'desc'>; limit?: integer; … } & Record<string, any>` | optional | Widget specific configuration |
8383
| **filterBindings** | `Record<string, string \| false>` | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |
@@ -182,7 +182,7 @@ Dashboard header action
182182
| **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) |
183183
| **dataset** | `string` | ✅ | Dataset name to bind (ADR-0021) |
184184
| **dimensions** | `string[]` | optional | Dimension names — X/group/split |
185-
| **values** | `string[]` | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
185+
| **values** | `string[]` | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family and on the single-series pie/donut/funnel/treemap/sankey; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
186186
| **layout** | `{ x: number; y: number; w: number; h: number }` | optional | Grid layout position (auto-flowed when omitted) |
187187
| **options** | `{ dateGranularity?: Enum<'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; sortBy?: string; sortOrder?: Enum<'asc' \| 'desc'>; limit?: integer; … } & Record<string, any>` | optional | Widget specific configuration |
188188
| **filterBindings** | `Record<string, string \| false>` | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |

‎packages/spec/api-surface/ui.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@
478478
"chartAggregateCategoryKey (function)",
479479
"chartAggregateResultKeys (function)",
480480
"chartAggregateValueKey (function)",
481-
"checkDashboardWidgetDimensionlessMeasureArity (function)",
481+
"checkDashboardWidgetChartMeasureArity (function)",
482482
"checkDashboardWidgetMetricMeasureArity (function)",
483483
"checkDashboardWidgetStageOrder (function)",
484484
"checkGlobalFilterDateDefaultValue (function)",

‎packages/spec/export-origins/ui.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@
463463
"chartAggregateCategoryKey": "src/ui/chart-aggregate.ts#chartAggregateCategoryKey (function)",
464464
"chartAggregateResultKeys": "src/ui/chart-aggregate.ts#chartAggregateResultKeys (function)",
465465
"chartAggregateValueKey": "src/ui/chart-aggregate.ts#chartAggregateValueKey (function)",
466-
"checkDashboardWidgetDimensionlessMeasureArity": "src/ui/dashboard.zod.ts#checkDashboardWidgetDimensionlessMeasureArity (function)",
466+
"checkDashboardWidgetChartMeasureArity": "src/ui/dashboard.zod.ts#checkDashboardWidgetChartMeasureArity (function)",
467467
"checkDashboardWidgetMetricMeasureArity": "src/ui/dashboard.zod.ts#checkDashboardWidgetMetricMeasureArity (function)",
468468
"checkDashboardWidgetStageOrder": "src/ui/dashboard.zod.ts#checkDashboardWidgetStageOrder (function)",
469469
"checkGlobalFilterDateDefaultValue": "src/ui/dashboard.zod.ts#checkGlobalFilterDateDefaultValue (function)",

‎packages/spec/src/migrations/registry.ts‎

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8891,12 +8891,16 @@ const step18: MigrationStep = {
88918891
+ '`id`, the number of measures and the authored `type`, when `dimensions` is absent or an empty '
88928892
+ 'array, `values` carries two or more measures, and `type` is `pie`, `donut`, `funnel`, '
88938893
+ '`scatter`, `radar`, `treemap` or `sankey`. The check is the exported '
8894-
+ '`checkDashboardWidgetDimensionlessMeasureArity`, and the set it reads is the exported '
8894+
+ '`checkDashboardWidgetChartMeasureArity` (exported as '
8895+
+ '`checkDashboardWidgetDimensionlessMeasureArity` until '
8896+
+ '`dashboard-widget-single-series-multi-measure-refused` gave it a second arm and renamed it), and '
8897+
+ 'the set it reads is the exported '
88958898
+ '`DASHBOARD_WIDGET_MULTI_MEASURE_TYPES` — one list, which the check, the refusal text and the '
88968899
+ '`values` doc string all read. '
8897-
+ 'WHAT IS NOT, so this is not read as complete: the same seven types WITH a dimension keep '
8898-
+ 'accepting several measures exactly as before (whether that shape renders them all is a '
8899-
+ 'separate question this entry does not answer); one measure parses on every type; every type in '
8900+
+ 'WHAT IS NOT, so this is not read as complete: the same seven types WITH a dimension are '
8901+
+ 'outside THIS entry — `scatter` and `radar` keep accepting several measures with a dimension, and '
8902+
+ '`pie` / `donut` / `funnel` / `treemap` / `sankey` are refused with a dimension too, by '
8903+
+ '`dashboard-widget-single-series-multi-measure-refused`; one measure parses on every type; every type in '
89008904
+ '`DASHBOARD_WIDGET_MULTI_MEASURE_TYPES` keeps accepting any number of measures with no '
89018905
+ 'dimension; the metric family (`metric` / `kpi` / `gauge` / `solid-gauge` / `bullet`, and a '
89028906
+ 'widget that declares no `type`, which resolves to `metric`) keeps its OWN refusal, unchanged '
@@ -9002,6 +9006,82 @@ const step18: MigrationStep = {
90029006
+ 'afterwards, because the two new tiles are numbers the dashboard was ALREADY paying '
90039007
+ 'to compute and had never shown.',
90049008
},
9009+
{
9010+
id: 'dashboard-widget-single-series-multi-measure-refused',
9011+
surface: 'dashboard widget measure arity WITH a dimension on a single-series chart type — '
9012+
+ '`dashboard.widgets[].values` (`DashboardWidgetSchema.values`) on a widget whose `dimensions` '
9013+
+ 'declares one or more dimensions and whose `type` is `pie`, `donut`, `funnel`, `treemap` or '
9014+
+ '`sankey`; and the check export `checkDashboardWidgetDimensionlessMeasureArity` (from '
9015+
+ '`@objectstack/spec/ui`), renamed `checkDashboardWidgetChartMeasureArity`',
9016+
replacement: 'Keep ONE measure on the widget, or pick a visual that renders several. With a '
9017+
+ 'dimension, `type: \'table\'` renders a column per measure and a bar-family type (`bar` / '
9018+
+ '`column` / `horizontal-bar`) renders one bar per measure in each category; both keep the '
9019+
+ 'unbounded `values` they have always had. Or keep the type and give each measure its OWN '
9020+
+ 'widget: a new `id`, the same `dataset` and `dimensions`, that one measure in `values`, and its '
9021+
+ 'own `layout` if the dashboard pins grid positions. ⛔ The migration does not do this for you '
9022+
+ 'and no conversion could: whether a two-measure pie by stage meant a table, a grouped bar chart '
9023+
+ 'or two pies is an authoring choice, and N widgets need N ids and N boxes on a 12-column grid, '
9024+
+ 'which is a LAYOUT decision about a dashboard the registry has never seen. A mirror that '
9025+
+ 'chained the old check export by name imports `checkDashboardWidgetChartMeasureArity` instead: '
9026+
+ 'same signature, same attachment point, and it refuses everything the old name refused.',
9027+
reason:
9028+
'Triage\'s ruling on objectui\'s finding that a dimensioned pie draws only its first measure: '
9029+
+ 'the spec refuses, the renderer does not invent. It extends '
9030+
+ '`dashboard-widget-dimensionless-multi-measure-refused`, which applied maintainer ruling D '
9031+
+ '(「协议不正确的应该先修改协议」) to a widget with NO dimension, to the dimensioned arm for '
9032+
+ 'the five types that draw one series whatever the dimension. Measured in objectui\'s shared '
9033+
+ 'chart renderer: the `pie` / `donut`, `funnel`, `treemap` and `sankey` arms each bind the '
9034+
+ 'first series and read no other, so `{ type: \'pie\', dimensions: [\'stage\'], values: '
9035+
+ '[\'revenue\', \'cost\'] }` drew one slice per stage for `revenue` and no trace of `cost` — the '
9036+
+ 'dataset query selects and computes every measure, and all but the first are thrown away. '
9037+
+ 'Every door accepted the document, because the dimensionless rule stepped aside for any '
9038+
+ 'widget that declared a dimension. That is the declared≠delivered shape ADR-0049 exists to '
9039+
+ 'end. A pie of several measures has zero measured pull, so no rendering is invented for it. '
9040+
+ 'The census before the change found zero authored dimensioned multi-measure widgets of the five '
9041+
+ 'types in the platform\'s examples, its first-party dashboards or objectui\'s example apps, so '
9042+
+ 'this ships at once with no deprecation window. Relaxing later is free and needs no second '
9043+
+ 'migration — a type whose renderer gains a declared rendering for several measures with a '
9044+
+ 'dimension leaves the single-series set — while leaving the shape accepted costs an author a '
9045+
+ 'widget that silently drops what they declared. The check export is renamed in the same change '
9046+
+ 'because its old name said a widget with a dimension was outside it, which stopped being true; '
9047+
+ 'no first-party consumer chained it under that name.',
9048+
acceptanceCriteria:
9049+
'⚠️ WHICH DOOR: the refusal is the spec\'s, attached at the same point as the dimensionless rule, '
9050+
+ 'so it reaches every door that parses the spec schema — measured on `defineStack`, which throws '
9051+
+ 'naming the widget, and on the stack schema, the dashboard schema and the `dashboard` '
9052+
+ 'metadata-type schema, each refusing at `widgets[N].values`. It is NOT refused by objectui\'s '
9053+
+ 'client-side authoring door until that door chains the export: `@object-ui/types` builds its '
9054+
+ '`DashboardWidgetSchema` from a `.shape` spread of the spec\'s, which carries the FIELDS and '
9055+
+ 'drops every object-level check, so its editor keeps accepting a dimensioned two-measure `pie` '
9056+
+ 'and the author meets the refusal at publish. ⇒ Do not read a green editor as a clean '
9057+
+ 'dashboard; re-parse through the spec. '
9058+
+ '⚠️ AND THE TODO CANNOT NAME YOUR MEASURES: a `SemanticMigration` is static prose emitted once '
9059+
+ 'per hop, with no per-document interpolation and no filtering by whether the stack carries the '
9060+
+ 'shape, so `os migrate meta` prints THIS paragraph, not a list of your widgets. The refusal is '
9061+
+ 'what names them, per widget, on the re-parse — drive the fix off `os validate`, not off the '
9062+
+ 'migrate output. '
9063+
+ 'WHAT IS REFUSED, exactly: ONE `custom` issue at `widgets[N].values`, naming the widget\'s '
9064+
+ '`id`, the number of measures and the authored `type`, and saying the type draws one series '
9065+
+ 'whatever its `dimensions`, when `dimensions` declares at least one dimension, `values` carries '
9066+
+ 'two or more measures, and `type` is `pie`, `donut`, `funnel`, `treemap` or `sankey`. The check '
9067+
+ 'is the exported `checkDashboardWidgetChartMeasureArity` — the dimensionless rule\'s own check, '
9068+
+ 'with a second arm; the single-series set it reads is not exported, and the refusal prints it. '
9069+
+ 'WHAT IS NOT, so this is not read as complete: `scatter` and `radar` WITH a dimension keep '
9070+
+ 'accepting several measures exactly as before (the ruling named the five: `radar` draws every '
9071+
+ 'series, and `scatter` says on the chart that it draws one); every type in '
9072+
+ '`DASHBOARD_WIDGET_MULTI_MEASURE_TYPES` keeps accepting any number of measures with a dimension; '
9073+
+ 'one measure parses on every type; a DIMENSIONLESS widget of the five keeps the dimensionless '
9074+
+ 'refusal, word for word and still ONE issue; the metric family (`metric` / `kpi` / `gauge` / '
9075+
+ '`solid-gauge` / `bullet`, and a widget that declares no `type`, which resolves to `metric`) '
9076+
+ 'keeps its OWN refusal, unchanged; an EMPTY `values` keeps the field\'s own `too_small`; a '
9077+
+ '`type` outside `ChartTypeSchema` reports the TYPE refusal alone, and called directly on a '
9078+
+ 'wider type enum the export judges only the types the spec declares; and whether each measure '
9079+
+ 'EXISTS in the bound dataset is still unreachable from this schema. '
9080+
+ 'VERIFY by re-parsing each dashboard: a dashboard that had one two-measure `pie` by stage should '
9081+
+ 'end with a `table` or bar-family widget carrying both measures, or with two widgets of one '
9082+
+ 'measure each — check the rendered grid afterwards, because the second measure is a number the '
9083+
+ 'dashboard was ALREADY paying to compute and had never shown.',
9084+
},
90059085
{
90069086
id: 'dashboard-widget-stage-order-non-funnel-refused',
90079087
surface: 'dashboard widget stage order — `dashboard.widgets[].options.stageOrder` '

0 commit comments

Comments
 (0)