@@ -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