Skip to content

Commit c1b2ff7

Browse files
os-zhuangclaude
andauthored
chore(spec): close react-block frontend-only divergence — overlay data/fields/options (ADR-0081) (#2488)
The conformance ratchet (#2485) accepted three frontend-only props in its baseline: ObjectChart `data`, ListView `fields`/`options`. These aren't real divergence — they're legitimate binding escape-hatches the component accepts that the spec schema simply doesn't model (static chart data; a simplified column list; a per-viewType options bag). The fix for "component accepts a prop the contract doesn't document" is to document it, not to leave it as accepted noise. - Add `data` (ObjectChart), `fields` + `options` (ListView) to the react-blocks overlay as `binding` props with honest descriptions, so the AI-facing contract (react-blocks.md / .contract.json) now declares them. - Regenerate the contract artifacts and the conformance baseline: every block is now 0 frontend-only. The ratchet is noise-free — any future frontend-only prop is a real, actionable signal rather than sitting in an accepted baseline. No public API-surface change (overlay entries, not new exports). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e6745bd commit c1b2ff7

4 files changed

Lines changed: 29 additions & 7 deletions

File tree

packages/spec/react-conformance.baseline.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,11 @@
66
"missing": false
77
},
88
"ListView": {
9-
"frontendOnly": [
10-
"fields",
11-
"options"
12-
],
9+
"frontendOnly": [],
1310
"missing": false
1411
},
1512
"ObjectChart": {
16-
"frontendOnly": [
17-
"data"
18-
],
13+
"frontendOnly": [],
1914
"missing": false
2015
},
2116
"RecordDetails": {

packages/spec/src/ui/react-blocks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export const REACT_BLOCKS: ReactBlockDef[] = [
9696
{ name: 'navigation', type: "{ mode: 'page' | 'drawer' | 'modal' | 'split' | 'none' }", kind: 'binding', description: 'What a row click does. Use { mode: \"none\" } when you handle clicks via onRowClick.' },
9797
{ name: 'onRowClick', type: '(record) => void', kind: 'callback', description: "Called with the clicked row's record — the hook for master/detail." },
9898
{ name: 'onNavigate', type: "(recordId, action: 'view' | 'edit') => void", kind: 'callback', description: 'Called for page-level navigation.' },
99+
{ name: 'fields', type: 'string[]', kind: 'binding', description: 'Limit/order the columns shown (defaults to the object list fields).' },
100+
{ name: 'options', type: 'Record<string, any>', kind: 'binding', description: 'View-type-specific options bag (kanban/calendar/gantt extras); prefer the typed spec props where they exist.' },
99101
],
100102
},
101103
{
@@ -108,6 +110,7 @@ export const REACT_BLOCKS: ReactBlockDef[] = [
108110
OBJECT_NAME,
109111
{ name: 'filter', type: 'FilterArray', kind: 'controlled', description: 'ObjectQL filter scoping the data; drive from React state.' },
110112
{ name: 'aggregate', type: '{ field, function, groupBy }', kind: 'binding', description: 'Aggregation: function (sum/avg/count) over field, grouped by groupBy.' },
113+
{ name: 'data', type: 'any[]', kind: 'binding', description: 'Static/precomputed data to chart directly instead of binding via objectName + aggregate.' },
111114
],
112115
},
113116
{

skills/objectstack-ui/contracts/react-blocks.contract.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,20 @@
179179
"required": false,
180180
"description": "What a row click does. Use { mode: \"none\" } when you handle clicks via onRowClick."
181181
},
182+
{
183+
"name": "fields",
184+
"type": "string[]",
185+
"kind": "binding",
186+
"required": false,
187+
"description": "Limit/order the columns shown (defaults to the object list fields)."
188+
},
189+
{
190+
"name": "options",
191+
"type": "Record<string, any>",
192+
"kind": "binding",
193+
"required": false,
194+
"description": "View-type-specific options bag (kanban/calendar/gantt extras); prefer the typed spec props where they exist."
195+
},
182196
{
183197
"name": "filters",
184198
"type": "FilterArray e.g. ['status','=','active']",
@@ -292,6 +306,13 @@
292306
"required": false,
293307
"description": "Aggregation: function (sum/avg/count) over field, grouped by groupBy."
294308
},
309+
{
310+
"name": "data",
311+
"type": "any[]",
312+
"kind": "binding",
313+
"required": false,
314+
"description": "Static/precomputed data to chart directly instead of binding via objectName + aggregate."
315+
},
295316
{
296317
"name": "filter",
297318
"type": "FilterArray",

skills/objectstack-ui/references/react-blocks.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Server-connected object table with toolbar and switchable visualizations (grid/k
4747
| `objectName` | `string` | binding || The object this block binds to (server-connected). |
4848
| `viewType` | `'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map'` | binding | | Which visualization to render (default grid). How you get a kanban/calendar/gantt of the object. |
4949
| `navigation` | `{ mode: 'page' \| 'drawer' \| 'modal' \| 'split' \| 'none' }` | binding | | What a row click does. Use { mode: "none" } when you handle clicks via onRowClick. |
50+
| `fields` | `string[]` | binding | | Limit/order the columns shown (defaults to the object list fields). |
51+
| `options` | `Record<string, any>` | binding | | View-type-specific options bag (kanban/calendar/gantt extras); prefer the typed spec props where they exist. |
5052
| `filters` | `FilterArray e.g. ['status','=','active']` | controlled | | ObjectQL base filter; drive from React state for tabbed/searched lists. ([field, op, value]; ops =, !=, >, <, contains, in; compound: ["and", [], []]). |
5153
| `onRowClick` | `(record) => void` | callback | | Called with the clicked row's record — the hook for master/detail. |
5254
| `onNavigate` | `(recordId, action: 'view' \| 'edit') => void` | callback | | Called for page-level navigation. |
@@ -69,6 +71,7 @@ Chart over an object’s aggregated data. Config props come from the spec Chart
6971
|------|------|------|:--------:|-------------|
7072
| `objectName` | `string` | binding || The object this block binds to (server-connected). |
7173
| `aggregate` | `{ field, function, groupBy }` | binding | | Aggregation: function (sum/avg/count) over field, grouped by groupBy. |
74+
| `data` | `any[]` | binding | | Static/precomputed data to chart directly instead of binding via objectName + aggregate. |
7275
| `filter` | `FilterArray` | controlled | | ObjectQL filter scoping the data; drive from React state. |
7376
| `showLegend` | `boolean` | data || Display legend |
7477
| `title` | `string` | data | | Chart title |

0 commit comments

Comments
 (0)