Skip to content

Commit fdf804d

Browse files
committed
feat(spec): the joined-report chart family's D3 semantic entry (ruling B)
ui-report-joined-chart-retired: the judgement half of report-joined-chart-removed. The D2 strip is lossless; what it cannot decide is whether the author meant a chart, which must then move to a non-joined report of its own. The changeset's ADR-0087 marker names both registrations. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5977052 commit fdf804d

3 files changed

Lines changed: 132 additions & 2 deletions

File tree

‎.changeset/20161-joined-report-chart-retired.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ untouched: it is that report's live embedded chart. A joined report with no
5454
load path) strips a block's `chart` and a joined container's `chart` from old
5555
sources and stored `sys_metadata` rows as a lossless delete. Stored rows can
5656
carry them: the Studio report form offered a block `chart` input until this
57-
change.
57+
change. The family's D3 semantic entry, `ui-report-joined-chart-retired`, states
58+
what the strip cannot decide: whether the chart was wanted. If it was, it moves
59+
to a non-joined report of its own, because a joined report has no chart channel.
5860
- **Form.** `reportForm` drops the block `chart` input and shows the container
5961
`chart` only when `type` is not `joined`; the `platform-objects` metadata-form
6062
translation bundles drop the `blocks.chart` label in all four locales.
@@ -66,4 +68,4 @@ untouched: it is that report's live embedded chart. A joined report with no
6668
non-joined reports and drops `chart` from the `blocks` row;
6769
`content/docs/ui/reports.mdx` lists what a joined container refuses.
6870

69-
<!-- adr-0087: registered report-joined-chart-removed -->
71+
<!-- adr-0087: registered report-joined-chart-removed, ui-report-joined-chart-retired -->
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
// #20161 — the judgement half of `report-joined-chart-removed`, owed under
6+
// #17152 ruling B (one D3 entry per retirement family, even when a lossless D2
7+
// exists). The D2 conversion strips a joined report's `chart` mechanically, at
8+
// both coordinates, and that strip changes nothing that renders. What it cannot
9+
// do is decide whether the author MEANT a chart: that intent has no place to go
10+
// on a joined report, so moving it to a report that draws one is the author's
11+
// call. Its own family, not an extension of
12+
// `ui-report-joined-container-selection-refused`: that entry is the enforce arm
13+
// for four selection keys that stay declared and has no D2 at all, while this one
14+
// retires a declared block key, refuses a container key, and pairs with a D2.
15+
export const entry: SemanticMigration = {
16+
id: 'ui-report-joined-chart-retired',
17+
surface:
18+
'`report.blocks[].chart` (REMOVED from the joined report block shape) and `report.chart` on '
19+
+ 'a report whose `type` is `joined` (REFUSED by `ReportSchema`\'s refinement) — a chart '
20+
+ 'anywhere on a joined report',
21+
replacement:
22+
'nothing on the joined report: a joined report draws each block as a table and has no chart '
23+
+ 'channel at either level. Delete the `chart`. If the chart was wanted, give the slice it '
24+
+ 'was meant to plot a report of its own — `type` `tabular`, `summary` or `matrix`, binding '
25+
+ 'the same `dataset` the block bound, selecting the dimension and measure the chart names '
26+
+ 'in its `rows` and `values` — carry the `chart` over to that report\'s top level, where '
27+
+ '`xAxis` names a dataset dimension and `yAxis` a measure exactly as before, and reach it '
28+
+ 'from the app navigation beside the joined report.',
29+
reason:
30+
'ADR-0049 enforce-or-remove. Nothing ever drew a chart on a joined report: the renderer\'s '
31+
+ 'joined branch draws each block as a table and returns before its one read of the '
32+
+ 'report\'s `chart`, and no renderer reads a block\'s `chart` at all — measured at this '
33+
+ 'repo\'s `.objectui-sha` pin `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52` '
34+
+ '(`DatasetReportRenderer.tsx`, joined branch at lines 1462-1524, the only chart read at '
35+
+ '1557). So both coordinates parsed, passed the `validate-chart-bindings` lint (which '
36+
+ 'resolved their axes as if they would plot), and showed tables only. The D2 conversion '
37+
+ '`report-joined-chart-removed` already REPAIRS THE DATA: it strips both from authored '
38+
+ 'sources on a chain replay and from stored `sys_metadata` rows at rehydration, a lossless '
39+
+ 'delete because neither value ever rendered. What it cannot repair is intent. Deleting '
40+
+ 'the key leaves the report looking exactly as it always did — which is the problem when '
41+
+ 'the author believed a chart was there: they were reading a chart that never existed, '
42+
+ 'and only they know whether they wanted one. A walker cannot move it anywhere either: a '
43+
+ 'joined report has no chart channel, and creating a new report, choosing its type and '
44+
+ 'placing it in navigation are authoring decisions, not rewrites. The Studio report form '
45+
+ 'offered a block chart input until this change, so a stored row carrying one is a real '
46+
+ 'shape, not a hypothetical. Ships at once, no deprecation window: there is no window in '
47+
+ 'which a key the renderer never reads does anything. `chart` on every non-joined report '
48+
+ 'is unchanged — it is that report\'s live embedded chart.',
49+
acceptanceCriteria:
50+
'WHICH DOOR: the refusal is the spec schema\'s, so it lands wherever a report is parsed '
51+
+ 'through `@objectstack/spec` — `defineReport`, `os validate` / `os build`, and the metadata '
52+
+ 'save door (the `report` entry of the metadata type registry, answered as '
53+
+ '`INVALID_METADATA` with status 422). A block `chart` is refused as an unrecognized key '
54+
+ 'on that block with the upgrade prescription; a container `chart` on a joined report is '
55+
+ 'one `custom` issue at `chart`. (1) No joined report carries a `chart` at either level: '
56+
+ 'run `os migrate meta --from 17` to list the mechanical strips for existing sources, and '
57+
+ 'the stored-row seams replay the same strip for rows at rest. (2) For every joined report '
58+
+ 'that carried one, decide whether the chart was wanted; if it was, a non-joined report '
59+
+ 'now binds that slice\'s dataset and carries the chart, and its `xAxis` / `yAxis` resolve '
60+
+ '(`validate-chart-bindings` checks them there). (3) Check the rendered joined report: it '
61+
+ 'renders exactly as before, because the chart was never drawn. A joined report with no '
62+
+ '`chart` parses byte-identically to before, and every non-joined report is untouched. '
63+
+ 'Census at the time of the change: zero joined reports with a chart in this repo\'s '
64+
+ 'example apps and in the hotcrm reference app, against a lit control (non-joined reports '
65+
+ 'carrying a chart: one and five).',
66+
};

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14704,6 +14704,68 @@ const step18: MigrationStep = {
1470414704
+ 'reports no `component-props-unknown-key` / `component-props-invalid` finding for the '
1470514705
+ 'rail.',
1470614706
},
14707+
// #20161 — the judgement half of `report-joined-chart-removed`, owed under
14708+
// #17152 ruling B (one D3 entry per retirement family, even when a lossless D2
14709+
// exists). The D2 conversion strips a joined report's `chart` mechanically, at
14710+
// both coordinates, and that strip changes nothing that renders. What it cannot
14711+
// do is decide whether the author MEANT a chart: that intent has no place to go
14712+
// on a joined report, so moving it to a report that draws one is the author's
14713+
// call. Its own family, not an extension of
14714+
// `ui-report-joined-container-selection-refused`: that entry is the enforce arm
14715+
// for four selection keys that stay declared and has no D2 at all, while this one
14716+
// retires a declared block key, refuses a container key, and pairs with a D2.
14717+
{
14718+
id: 'ui-report-joined-chart-retired',
14719+
surface:
14720+
'`report.blocks[].chart` (REMOVED from the joined report block shape) and `report.chart` on '
14721+
+ 'a report whose `type` is `joined` (REFUSED by `ReportSchema`\'s refinement) — a chart '
14722+
+ 'anywhere on a joined report',
14723+
replacement:
14724+
'nothing on the joined report: a joined report draws each block as a table and has no chart '
14725+
+ 'channel at either level. Delete the `chart`. If the chart was wanted, give the slice it '
14726+
+ 'was meant to plot a report of its own — `type` `tabular`, `summary` or `matrix`, binding '
14727+
+ 'the same `dataset` the block bound, selecting the dimension and measure the chart names '
14728+
+ 'in its `rows` and `values` — carry the `chart` over to that report\'s top level, where '
14729+
+ '`xAxis` names a dataset dimension and `yAxis` a measure exactly as before, and reach it '
14730+
+ 'from the app navigation beside the joined report.',
14731+
reason:
14732+
'ADR-0049 enforce-or-remove. Nothing ever drew a chart on a joined report: the renderer\'s '
14733+
+ 'joined branch draws each block as a table and returns before its one read of the '
14734+
+ 'report\'s `chart`, and no renderer reads a block\'s `chart` at all — measured at this '
14735+
+ 'repo\'s `.objectui-sha` pin `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52` '
14736+
+ '(`DatasetReportRenderer.tsx`, joined branch at lines 1462-1524, the only chart read at '
14737+
+ '1557). So both coordinates parsed, passed the `validate-chart-bindings` lint (which '
14738+
+ 'resolved their axes as if they would plot), and showed tables only. The D2 conversion '
14739+
+ '`report-joined-chart-removed` already REPAIRS THE DATA: it strips both from authored '
14740+
+ 'sources on a chain replay and from stored `sys_metadata` rows at rehydration, a lossless '
14741+
+ 'delete because neither value ever rendered. What it cannot repair is intent. Deleting '
14742+
+ 'the key leaves the report looking exactly as it always did — which is the problem when '
14743+
+ 'the author believed a chart was there: they were reading a chart that never existed, '
14744+
+ 'and only they know whether they wanted one. A walker cannot move it anywhere either: a '
14745+
+ 'joined report has no chart channel, and creating a new report, choosing its type and '
14746+
+ 'placing it in navigation are authoring decisions, not rewrites. The Studio report form '
14747+
+ 'offered a block chart input until this change, so a stored row carrying one is a real '
14748+
+ 'shape, not a hypothetical. Ships at once, no deprecation window: there is no window in '
14749+
+ 'which a key the renderer never reads does anything. `chart` on every non-joined report '
14750+
+ 'is unchanged — it is that report\'s live embedded chart.',
14751+
acceptanceCriteria:
14752+
'WHICH DOOR: the refusal is the spec schema\'s, so it lands wherever a report is parsed '
14753+
+ 'through `@objectstack/spec` — `defineReport`, `os validate` / `os build`, and the metadata '
14754+
+ 'save door (the `report` entry of the metadata type registry, answered as '
14755+
+ '`INVALID_METADATA` with status 422). A block `chart` is refused as an unrecognized key '
14756+
+ 'on that block with the upgrade prescription; a container `chart` on a joined report is '
14757+
+ 'one `custom` issue at `chart`. (1) No joined report carries a `chart` at either level: '
14758+
+ 'run `os migrate meta --from 17` to list the mechanical strips for existing sources, and '
14759+
+ 'the stored-row seams replay the same strip for rows at rest. (2) For every joined report '
14760+
+ 'that carried one, decide whether the chart was wanted; if it was, a non-joined report '
14761+
+ 'now binds that slice\'s dataset and carries the chart, and its `xAxis` / `yAxis` resolve '
14762+
+ '(`validate-chart-bindings` checks them there). (3) Check the rendered joined report: it '
14763+
+ 'renders exactly as before, because the chart was never drawn. A joined report with no '
14764+
+ '`chart` parses byte-identically to before, and every non-joined report is untouched. '
14765+
+ 'Census at the time of the change: zero joined reports with a chart in this repo\'s '
14766+
+ 'example apps and in the hotcrm reference app, against a lit control (non-joined reports '
14767+
+ 'carrying a chart: one and five).',
14768+
},
1470714769
{
1470814770
id: 'ui-report-joined-container-selection-refused',
1470914771
surface: 'report selection keys on a `joined` container — a top-level `dataset`, or a '

0 commit comments

Comments
 (0)