Skip to content

Commit 8f79379

Browse files
os-litantclaude
andauthored
feat(spec): converge ComponentPropsMap['object-grid'].data onto ViewDataSchema (ui#6207 Option A); record the ui#6206-B measurement fork (#12120)
* feat(spec): converge ComponentPropsMap object-grid data onto ViewDataSchema (objectui-6207 Option A) Accept-set change on one props-map entry: data was z.array(z.unknown()), now ViewDataSchema (provider-discriminated object). Static inline rows move to { provider: 'value', items: [...] }. ADR-0087 semantic entry object-grid-data-view-data-converged carries the prescription; sweep found zero bare-array authors in first-party corpora. The element:number.filter sibling key of the same card is deliberately untouched (measurement-first fork; see the card report). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV * feat(spec): ObjectGridPropsParsed alias — object-grid leaves the default-free pin family (ADR-0122) ViewDataSchema's own input/infer split rides into ObjectGridPropsSchema, so Iso839 is deleted per its comment's prescription and the Parsed alias is declared. Regenerates api-surface, export-origins and docs references. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV * test(spec): record Iso839's departure in the pin count (833 -> 832) with its receipt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV * chore(spec): regenerate api-surface, export-origins, docs on the merged tree Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc8627e commit 8f79379

9 files changed

Lines changed: 200 additions & 10 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): `ComponentPropsMap['object-grid'].data` converges onto `ViewDataSchema` (#12039, objectui#6207 Option A)
6+
7+
**BREAKING** accept-set change on one props-map entry, shipped as `minor` under
8+
the repo's launch-window convention for breaking changes.
9+
10+
Two spec authorities disagreed on the KIND of `object-grid`'s `data`:
11+
`ViewDataSchema` — the authority objectui#5090 ruled the registry declaration
12+
against, and what `ObjectGridSchema.data` resolves to — is an object
13+
discriminated on `provider` (`object` / `api` / `value` / `schema`), while
14+
`ComponentPropsMap['object-grid'].data` said `z.array(z.unknown())`. Measured
15+
on `@objectstack/spec@17.2.0`: `{ provider: 'value', items: [] }` — the
16+
pinned-legal form — was refused by the props-map entry while the bare array
17+
parsed. Maintainer ruling (2026-08-25, objectui#6207, Option A): the props-map
18+
entry converges onto `ViewDataSchema`; the bare-array form is the deprecated
19+
`staticData` shortcut the objectui#4648 carve-out already refuses to publish.
20+
21+
Migration — FROM → TO, one wrapping object:
22+
23+
```ts
24+
// before (refused now)
25+
data: [{ id: 1, title: 'Inline row' }]
26+
// after
27+
data: { provider: 'value', items: [{ id: 1, title: 'Inline row' }] }
28+
```
29+
30+
The ruled migration check ran with the change: the sweep of generated
31+
artifacts, templates and first-party corpora (examples/, skills/,
32+
create-objectstack, spec fixtures) found zero bare-array `data` authors, so no
33+
rewrite ships. `staticData` (the legacy bare-array shortcut the renderer still
34+
reads) keeps its shape but is not the prescription.
35+
36+
`ComponentPropsMap['element:number'].filter` (the sibling key of #12039 /
37+
objectui#6206) is NOT changed here: the ruling's binding measurement-first
38+
precondition measured the pinned adapter/runtime refusing the raw
39+
`ViewFilterRule[]` form on the element's primary (analytics) read path, which
40+
forks that key back to triage. objectui#6206 remains open.
41+
42+
<!-- adr-0087: registered object-grid-data-view-data-converged -->

content/docs/references/ui/component.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ const result = AIChatWindowProps.parse(data);
302302
| **showColumnTypeIcons** | `boolean` | optional | Show field-type icons in column headers |
303303
| **exportOptions** | `any` | optional | Export config (`{ formats, streaming }`) |
304304
| **operations** | `any` | optional | Operation toggles (`{ export: false, … }`) |
305-
| **data** | `any[]` | optional | Static inline rows — bypasses the object query |
306-
| **staticData** | `any[]` | optional | Alternate spelling of `data` the renderer also reads |
305+
| **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record<string, any> }` | optional | Data source binding (ViewDataSchema — discriminated on `provider`: object \| api \| value \| schema). Static inline rows live at `{ provider: 'value', items: [...] }`; the bare-array shortcut is refused — see migration `object-grid-data-view-data-converged` |
306+
| **staticData** | `any[]` | optional | Deprecated bare-array static-rows shortcut the renderer still reads. Prefer `data: { provider: 'value', items: [...] }` |
307307

308308

309309
---

packages/spec/api-surface/ui.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
"ObjectFormProps (type)",
235235
"ObjectFormPropsSchema (const)",
236236
"ObjectGridProps (type)",
237+
"ObjectGridPropsParsed (type)",
237238
"ObjectGridPropsSchema (const)",
238239
"ObjectKanbanProps (type)",
239240
"ObjectKanbanPropsSchema (const)",

packages/spec/export-origins/ui.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
"ObjectFormProps": "src/ui/component.zod.ts#ObjectFormProps (type)",
235235
"ObjectFormPropsSchema": "src/ui/component.zod.ts#ObjectFormPropsSchema (const)",
236236
"ObjectGridProps": "src/ui/component.zod.ts#ObjectGridProps (type)",
237+
"ObjectGridPropsParsed": "src/ui/component.zod.ts#ObjectGridPropsParsed (type)",
237238
"ObjectGridPropsSchema": "src/ui/component.zod.ts#ObjectGridPropsSchema (const)",
238239
"ObjectKanbanProps": "src/ui/component.zod.ts#ObjectKanbanProps (type)",
239240
"ObjectKanbanPropsSchema": "src/ui/component.zod.ts#ObjectKanbanPropsSchema (const)",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
export const entry: SemanticMigration = {
6+
id: 'object-grid-data-view-data-converged',
7+
surface:
8+
"`object-grid` component props — `data` (the KIND: bare array `z.array(z.unknown())` "
9+
+ 'vs the `ViewDataSchema` provider object)',
10+
replacement:
11+
"`ViewDataSchema` — the provider-discriminated object (`provider: 'object' | 'api' | "
12+
+ "'value' | 'schema'`). Static inline rows move from `data: [...]` to "
13+
+ "`data: { provider: 'value', items: [...] }` — the same rows, wrapped in the one "
14+
+ 'arm that means "hardcoded data array". The other three arms are unchanged '
15+
+ '`ViewDataSchema` semantics; `staticData` (the deprecated bare-array shortcut the '
16+
+ 'renderer still reads) keeps its shape but is not the prescription',
17+
reason:
18+
'Two entries of one contract disagreed on the KIND (objectui#6207, contract-vs-'
19+
+ "contract): `ComponentPropsMap['object-grid'].data` said bare array ('Static inline "
20+
+ "rows — bypasses the object query') while `ViewDataSchema` — the authority "
21+
+ 'objectui#5090 ruled the registry declaration against, pinned by '
22+
+ '`gridDataInputContract.test.ts`, and what `ObjectGridSchema.data` resolves to — is '
23+
+ "an object discriminated on `provider`. Measured on @objectstack/spec@17.2.0: "
24+
+ "`{ provider: 'value', items: [] }` — the pinned-legal form — was REFUSED by the "
25+
+ 'props-map entry (`expected array, received object`) while the bare array parsed. '
26+
+ 'Whichever authority a value satisfied, the other refused it, and the objectui '
27+
+ 'parity gate had to carry the reasoned exemption `object-grid.data:object` to look '
28+
+ 'away. The maintainer ruling (2026-08-25, batch adjudication batch 4; verbatim: '
29+
+ '「同意」, Option A) converged the props-map entry onto `ViewDataSchema`; the '
30+
+ 'bare-array form is the deprecated `staticData` shortcut the objectui#4648 '
31+
+ 'carve-out already refuses to publish. The ruled migration check ran with the '
32+
+ 'change: the sweep of generated artifacts, templates and first-party corpora '
33+
+ '(examples/, skills/, create-objectstack, spec fixtures) found ZERO bare-array '
34+
+ '`data` authors, so no rewrite ships — this entry carries the prescription for '
35+
+ 'authors outside the repo.',
36+
acceptanceCriteria:
37+
"`ComponentPropsMap['object-grid'].safeParse({ data: { provider: 'value', items: [] } })` "
38+
+ 'succeeds (and the other `ViewDataSchema` arms parse through the same entry); a '
39+
+ 'bare-array `data: [...]` is refused at the `data` path. An author carrying '
40+
+ "`data: [...]` writes `data: { provider: 'value', items: [...] }` — same rows, "
41+
+ 'one wrapping object. Downstream (objectui, after a released spec version reaches '
42+
+ 'the pin): the `object-grid.data:object` exemption entry in '
43+
+ '`registry-inputs-spec-parity.test.ts` becomes deletable, which is what closes '
44+
+ 'objectui#6207.',
45+
};

packages/spec/src/migrations/registry.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6058,6 +6058,47 @@ const step18: MigrationStep = {
60586058
+ 'set stays exactly `DEFAULT_METADATA_TYPE_REGISTRY` plus item-population growth, '
60596059
+ 'before and after.',
60606060
},
6061+
{
6062+
id: 'object-grid-data-view-data-converged',
6063+
surface:
6064+
"`object-grid` component props — `data` (the KIND: bare array `z.array(z.unknown())` "
6065+
+ 'vs the `ViewDataSchema` provider object)',
6066+
replacement:
6067+
"`ViewDataSchema` — the provider-discriminated object (`provider: 'object' | 'api' | "
6068+
+ "'value' | 'schema'`). Static inline rows move from `data: [...]` to "
6069+
+ "`data: { provider: 'value', items: [...] }` — the same rows, wrapped in the one "
6070+
+ 'arm that means "hardcoded data array". The other three arms are unchanged '
6071+
+ '`ViewDataSchema` semantics; `staticData` (the deprecated bare-array shortcut the '
6072+
+ 'renderer still reads) keeps its shape but is not the prescription',
6073+
reason:
6074+
'Two entries of one contract disagreed on the KIND (objectui#6207, contract-vs-'
6075+
+ "contract): `ComponentPropsMap['object-grid'].data` said bare array ('Static inline "
6076+
+ "rows — bypasses the object query') while `ViewDataSchema` — the authority "
6077+
+ 'objectui#5090 ruled the registry declaration against, pinned by '
6078+
+ '`gridDataInputContract.test.ts`, and what `ObjectGridSchema.data` resolves to — is '
6079+
+ "an object discriminated on `provider`. Measured on @objectstack/spec@17.2.0: "
6080+
+ "`{ provider: 'value', items: [] }` — the pinned-legal form — was REFUSED by the "
6081+
+ 'props-map entry (`expected array, received object`) while the bare array parsed. '
6082+
+ 'Whichever authority a value satisfied, the other refused it, and the objectui '
6083+
+ 'parity gate had to carry the reasoned exemption `object-grid.data:object` to look '
6084+
+ 'away. The maintainer ruling (2026-08-25, batch adjudication batch 4; verbatim: '
6085+
+ '「同意」, Option A) converged the props-map entry onto `ViewDataSchema`; the '
6086+
+ 'bare-array form is the deprecated `staticData` shortcut the objectui#4648 '
6087+
+ 'carve-out already refuses to publish. The ruled migration check ran with the '
6088+
+ 'change: the sweep of generated artifacts, templates and first-party corpora '
6089+
+ '(examples/, skills/, create-objectstack, spec fixtures) found ZERO bare-array '
6090+
+ '`data` authors, so no rewrite ships — this entry carries the prescription for '
6091+
+ 'authors outside the repo.',
6092+
acceptanceCriteria:
6093+
"`ComponentPropsMap['object-grid'].safeParse({ data: { provider: 'value', items: [] } })` "
6094+
+ 'succeeds (and the other `ViewDataSchema` arms parse through the same entry); a '
6095+
+ 'bare-array `data: [...]` is refused at the `data` path. An author carrying '
6096+
+ "`data: [...]` writes `data: { provider: 'value', items: [...] }` — same rows, "
6097+
+ 'one wrapping object. Downstream (objectui, after a released spec version reaches '
6098+
+ 'the pin): the `object-grid.data:object` exemption entry in '
6099+
+ '`registry-inputs-spec-parity.test.ts` becomes deletable, which is what closes '
6100+
+ 'objectui#6207.',
6101+
},
60616102
{
60626103
id: 'object-index-unknown-keys-refused',
60636104
surface: 'object `indexes[]` entries (`IndexSchema`) — undeclared keys',

packages/spec/src/type-alias-convention.pin.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ import type * as M170 from './ui/component.zod.js';
267267
import type * as M183 from './api/sortability.zod.js';
268268

269269
// ---------------------------------------------------------------------------
270-
// 835 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared.
270+
// 834 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared.
271271
//
272272
// That number is machine-checked, not hand-kept. The runtime companion at the
273273
// bottom of this file recomputes the pin count from the source and asserts that
@@ -1568,9 +1568,11 @@ export type Iso849 = Assert<Eq< z.input< typeof M170.RecordHistoryProps >, z.inf
15681568
// The object-* block family (#7751) — deliberately default-free in its first,
15691569
// warning-tier step ("the author said nothing" must stay distinguishable from
15701570
// "the author asked for the renderer's fallback"), so input === infer holds.
1571-
// A default added to any of these six goes red here, and the fix is the ADR's:
1571+
// A default added to any of these goes red here, and the fix is the ADR's:
15721572
// declare the XParsed alias and delete the pin line.
1573-
export type Iso839 = Assert<Eq< z.input< typeof M170.ObjectGridPropsSchema >, z.infer< typeof M170.ObjectGridPropsSchema > >>;
1573+
// `ObjectGridPropsSchema` (Iso839) left the family exactly that way on the
1574+
// ui#6207 convergence: its `data` now carries `ViewDataSchema`, whose own
1575+
// input ≠ infer, so `ObjectGridPropsParsed` is declared and the pin deleted.
15741576
export type Iso840 = Assert<Eq< z.input< typeof M170.ObjectMetricPropsSchema >, z.infer< typeof M170.ObjectMetricPropsSchema > >>;
15751577
export type Iso841 = Assert<Eq< z.input< typeof M170.ObjectKanbanPropsSchema >, z.infer< typeof M170.ObjectKanbanPropsSchema > >>;
15761578
export type Iso842 = Assert<Eq< z.input< typeof M170.ObjectCalendarPropsSchema >, z.infer< typeof M170.ObjectCalendarPropsSchema > >>;
@@ -1670,7 +1672,7 @@ describe('ADR-0122 type-alias convention', () => {
16701672
// this title and the section header above the pin list — are now asserted
16711673
// against the recomputed count below, so neither can go stale without a red
16721674
// test naming it.
1673-
it('still declares all 835 isomorphic pins', () => {
1675+
it('still declares all 834 isomorphic pins', () => {
16741676
// The truth of each pin is proved by tsc, not here — an `Assert<Eq<...>>`
16751677
// that stops holding is a compile error with the alias named. What tsc
16761678
// cannot notice is a pin that was DELETED: removing the assertion removes
@@ -1970,9 +1972,16 @@ describe('ADR-0122 type-alias convention', () => {
19701972
// `.pipe()` anywhere in either tree, so the two shapes coincide and
19711973
// ADR-0122 gives each a pin rather than an `XParsed`. Ids `Iso857`/
19721974
// `Iso858`, the next free ones — ids are claims about pins, not positions.
1975+
//
1976+
// 835 -> 834 is #12039's ui#6207 convergence: `ObjectGridPropsSchema.data`
1977+
// now carries `ViewDataSchema`, whose own input ≠ infer (measured — the
1978+
// Eq probe answers false on ViewDataSchema alone), so `object-grid` left
1979+
// the default-free object-* family exactly the way that family's comment
1980+
// prescribes: `ObjectGridPropsParsed` declared, the Iso839 pin deleted.
1981+
// -1 converted to an `XParsed` pair; the Iso number stays vacant.
19731982
const self = readFileSync(fileURLToPath(import.meta.url), 'utf8');
19741983
const pins = self.match(/^export type Iso\d+ = Assert</gm) ?? [];
1975-
expect(pins).toHaveLength(835);
1984+
expect(pins).toHaveLength(834);
19761985

19771986
// The count is stated in PROSE twice as well — this case's title and the
19781987
// section header above the pin list — and until #6605 nothing read either

packages/spec/src/ui/component.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,34 @@ describe('#7751 — object-* block props schemas', () => {
21212121
expect(parsed.filter).toEqual([['owner_id', '=', '{current_user_id}']]);
21222122
});
21232123

2124+
it("object-grid `data` takes the ViewDataSchema provider object — the ui#6207 convergence (Option A)", () => {
2125+
// The #5090-pinned authority: static inline rows are `{ provider: 'value',
2126+
// items }`. Before the 2026-08-25 ruling this exact value was REFUSED by
2127+
// this entry ("expected array, received object") while being the
2128+
// pinned-legal form of the authority the objectui declaration is held to.
2129+
const inline = ComponentPropsMap['object-grid'].safeParse({
2130+
data: { provider: 'value', items: [] },
2131+
});
2132+
expect(inline.success).toBe(true);
2133+
// A second arm of the union, to prove the whole discriminated authority is
2134+
// reachable through this entry rather than one hardcoded branch.
2135+
const bound = ComponentPropsMap['object-grid'].safeParse({
2136+
data: { provider: 'object', object: 'showcase_task' },
2137+
});
2138+
expect(bound.success).toBe(true);
2139+
});
2140+
2141+
it('the bare-array `data` — the deprecated `staticData` shortcut — is REFUSED at the `data` path', () => {
2142+
// Reverse verification of the convergence: the value this entry used to
2143+
// accept (`z.array(z.unknown())`) no longer parses. The #4648 carve-out
2144+
// already refuses to publish the bare-array author; this closes the spec
2145+
// entry that still advertised it. Migration:
2146+
// `object-grid-data-view-data-converged`.
2147+
const r = ComponentPropsMap['object-grid'].safeParse({ data: [{ id: 1 }] });
2148+
expect(r.success).toBe(false);
2149+
expect(r.error!.issues.some((i) => i.path[0] === 'data')).toBe(true);
2150+
});
2151+
21242152
it('`defaultFilters` stays HONOURED — it is a read legacy fallback, not an inert spelling', () => {
21252153
// ObjectGrid.tsx reads it and lowers it to `$filter` when `filter` is
21262154
// absent (the routed finding on #7751 verified the read point). Only the

packages/spec/src/ui/component.zod.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { z } from 'zod';
44
import { FilterConditionSchema } from '../data/filter.zod';
5-
import { ViewFilterRuleSchema } from './view.zod';
5+
import { ViewFilterRuleSchema, ViewDataSchema } from './view.zod';
66
import { InlineActionSchema, ActionLocationSchema } from './action.zod';
77
import { I18nLabelSchema, AriaPropsSchema } from './i18n.zod';
88
import { FeedItemType, FeedFilterMode } from '../data/feed.zod';
@@ -2263,11 +2263,34 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({
22632263
showColumnTypeIcons: z.boolean().optional().describe('Show field-type icons in column headers'),
22642264
exportOptions: z.unknown().optional().describe('Export config ({ formats, streaming })'),
22652265
operations: z.unknown().optional().describe('Operation toggles ({ export: false, … })'),
2266-
data: z.array(z.unknown()).optional().describe('Static inline rows — bypasses the object query'),
2267-
staticData: z.array(z.unknown()).optional().describe('Alternate spelling of `data` the renderer also reads'),
2266+
/**
2267+
* Data source binding — `ViewDataSchema`, the #5090-pinned authority the
2268+
* objectui registry declares against (`plugin-grid/src/index.tsx:225`
2269+
* `type: 'object'`, held by `gridDataInputContract.test.ts`). Until the
2270+
* ui#6207 ruling (2026-08-25, Option A: 「同意」) this entry said
2271+
* `z.array(z.unknown())` — the bare-array spelling of the deprecated
2272+
* `staticData` shortcut — so the two spec authorities refused each other's
2273+
* legal values: this entry accepted `data: [{…}]` and refused
2274+
* `{ provider: 'value', items: [] }`, while `ViewDataSchema` (what
2275+
* `ObjectGridSchema.data` resolves to, and what the designer publishes)
2276+
* ruled the opposite. Static inline rows live at
2277+
* `{ provider: 'value', items: [...] }`; the migration prescription is the
2278+
* `object-grid-data-view-data-converged` semantic entry.
2279+
*/
2280+
data: ViewDataSchema.optional()
2281+
.describe("Data source binding (ViewDataSchema — discriminated on `provider`: object | api | value | schema). Static inline rows live at `{ provider: 'value', items: [...] }`; the bare-array shortcut is refused — see migration `object-grid-data-view-data-converged`"),
2282+
staticData: z.array(z.unknown()).optional().describe("Deprecated bare-array static-rows shortcut the renderer still reads. Prefer `data: { provider: 'value', items: [...] }`"),
22682283
}));
22692284
/** Author state (ADR-0122: the bare name is the author state). */
22702285
export type ObjectGridProps = z.input<typeof ObjectGridPropsSchema>;
2286+
/**
2287+
* ADR-0122: the parsed state differs from the authored state on exactly one
2288+
* key — `data` carries `ViewDataSchema` (the ui#6207 convergence), whose own
2289+
* input ≠ infer. So `object-grid` leaves the type-alias convention pin's
2290+
* default-free family (the Iso839 line deleted with this alias), taking the
2291+
* `RecordAlertPropsParsed` route its comment prescribes.
2292+
*/
2293+
export type ObjectGridPropsParsed = z.infer<typeof ObjectGridPropsSchema>;
22712294

22722295
/**
22732296
* `object-metric` (objectui `plugin-dashboard/src/ObjectMetricWidget.tsx` @

0 commit comments

Comments
 (0)