diff --git a/.changeset/23789-page-props-save-door-sentences.md b/.changeset/23789-page-props-save-door-sentences.md new file mode 100644 index 00000000000..9acc002a709 --- /dev/null +++ b/.changeset/23789-page-props-save-door-sentences.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +docs(spec): the step-18 upgrade text and the `exportOptions` TSDoc say a stored page's next save meets the component-props gate at the metadata save door + +Clause-②: no + +Text only; no accept set moves. Since the component-props gate (`validateComponentProps` / `validateComponentTypes`) runs on a `page` write at the metadata save door, three step-18 semantic entries that `os migrate meta` and the protocol upgrade guide print — `ui-page-action-row-operation-declared`, `ui-object-block-grouping-config-typed` and `element-text-variant-heading-subheading-retired` — and the `object-grid` `exportOptions` TSDoc in the `.d.ts` still said a page component's `properties` is not parsed on the save path. They now say what runs: a stored page still loads, because `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door (422 `INVALID_METADATA` on a finding), beside `os validate`, `os build` and `os lint`. diff --git a/content/docs/deployment/cli.mdx b/content/docs/deployment/cli.mdx index a85386649b4..24bbad1632c 100644 --- a/content/docs/deployment/cli.mdx +++ b/content/docs/deployment/cli.mdx @@ -1491,7 +1491,7 @@ can produce, because both supply a live one. | Rows that still fail the current schema after conversion | That is a genuine contract violation, not chain-owned history. The write path's rejection is correct; fix the row in Studio | | A flow whose rename the conflict guard refused | The old node-type token is a live name something else owns here. Rewriting would clobber that owner, so the row fails loudly naming the token — never a silent skip | | A site the conversion chain leaves as stored because no lossless rewrite exists — above all a page filter carrying `$and` / `$or` / `$not` | Flattening a combinator changes which rows the page selects, so it is never done. Each site is printed as a `TODO` line under its row — path, block, and what blocks the rewrite — whatever the row's outcome; a row with nothing but TODOs is reported `skipped`. It does not fail the run, since no run of this pass can clear it: rewrite each site by hand | -| A stored shape protocol 18 refuses and no conversion rewrites — a `controlled_by_parent` object without exactly one required `master_detail` (two masters, none, a required `lookup` only, an explicit `required: false`), a view bound to the retired view-level API data source or a page component whose `data` prop binds it (`object-grid`, `object-map`, `object-gantt`, `object-tree`), a flow value slot carrying a `{…}` token (a `get_record` / `update_record` / `delete_record` filter operand among them) | Each would need a choice only its owner can make — which master, which page, what an absent value should do — so it is never rewritten. Each site is printed as a `TODO` line under its row, naming the step-18 entry that refuses it, the object, view, page component or flow node, and the owner's choices; a binding's request headers are withheld. Until it is rewritten by hand, the row is refused: an object registers but derives no master, a view registers under `[metadata_spec_invalid]`, a page still loads and saves while `os validate`, `os build` and `os lint` refuse the binding in source, and a flow is not registered | +| A stored shape protocol 18 refuses and no conversion rewrites — a `controlled_by_parent` object without exactly one required `master_detail` (two masters, none, a required `lookup` only, an explicit `required: false`), a view bound to the retired view-level API data source or a page component whose `data` prop binds it (`object-grid`, `object-map`, `object-gantt`, `object-tree`), a flow value slot carrying a `{…}` token (a `get_record` / `update_record` / `delete_record` filter operand among them) | Each would need a choice only its owner can make — which master, which page, what an absent value should do — so it is never rewritten. Each site is printed as a `TODO` line under its row, naming the step-18 entry that refuses it, the object, view, page component or flow node, and the owner's choices; a binding's request headers are withheld. Until it is rewritten by hand, the row is refused: an object registers but derives no master, a view registers under `[metadata_spec_invalid]`, a page still loads, its next save is refused at the metadata save door (422), and `os validate`, `os build` and `os lint` refuse the binding in source, and a flow is not registered | | Any other site `registerFlow` refuses in a stored flow the parse accepts — a text slot's template hole, a decision's `mode`, a malformed predicate, a plugin node's undeclared config key, an `api` flow with no secret | The flow's own definition is wrong, and no conversion rewrites it. Each site is printed as a `TODO registerFlow` line under its row, in the words `registerFlow` refuses it with; a site already listed under a step-18 entry is not repeated. Until it is fixed, the flow is not registered at boot and never runs. A flow the parse itself refuses is reported among the rows that could not be rewritten, in the parse's words | **One thing it lists and never writes: decision nodes that changed meaning at diff --git a/packages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.ts b/packages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.ts index 676def812d8..e53613c7348 100644 --- a/packages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.element-text-variant-heading-subheading-retired.ts @@ -32,10 +32,11 @@ export const entry: SemanticMigration = { + 'and `subheading` in a medium-weight small heading style, and `h2` / `h3` draw their own, larger ' + 'styles. Whether the page wanted that level is the author\'s call — a heading placed for its ' + 'size rather than its place in the outline may want a deeper level. Nothing is dropped at rest: ' - + 'a stored page replays the rewrite at rehydration; a page component\'s `properties` is not ' - + 'parsed on the save path, and the component-props gate reports an old spelling as a ' + + 'a stored page replays the rewrite at rehydration and still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the ' + + 'component-props gate at the metadata save door. The gate reports an old spelling as a ' + '`component-props-invalid` error, carrying the prescription, on `os validate`, `os build` and ' - + '`os lint`. ADR-0087', + + '`os lint` and at that save door. ADR-0087', acceptanceCriteria: 'No `element:text` page component carries `variant` `heading` or `subheading`; `os validate` ' + 'reports no `component-props-invalid` finding under `properties.variant` for these blocks. For ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts index 188f4d8afc2..a57dfbd9dd1 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts @@ -10,9 +10,10 @@ import type { SemanticMigration } from '../../types.js'; // key, and #21704's fork 5 A refused the member's `properties.params`, so a // member has no static-values spelling and the container drops a non-array // `params` on any other type at run time. D3 only: page-component `properties` -// is not parsed on the metadata save or load path, so a stored page is never -// refused at load; there is no D2 conversion, because the only home for static values -// is a different node (an `action:button`), which no rewrite can build in the +// is not parsed on the metadata load path, so a stored page is never refused at +// load (its next save meets the component-props gate at the metadata save +// door); there is no D2 conversion, because the only home for static values is +// a different node (an `action:button`), which no rewrite can build in the // author's place; and the census found no writer to respell. export const entry: SemanticMigration = { id: 'ui-action-group-menu-member-params-array-only', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts index 963f3d7c171..03ce9983499 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts @@ -9,10 +9,11 @@ import type { SemanticMigration } from '../../types.js'; // with the rows' prescriptions; `outcomeMessages`, a member `className` and // `properties.params` refused; `outcomeMessages` undeclared on all four action // blocks. D3 only: page-component `properties` is not parsed on the metadata -// save or load path, so a stored page is never refused at load; and the authored census +// load path, so a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door); and the authored census // found no working member to respell — the refused values are objectui's probes -// of the very reads the ruling refuses (a member `className`, `outcomeMessages`, -// `properties.params`) and of the host's `autoTrigger` flag. +// of the very reads the ruling refuses (a member `className`, +// `outcomeMessages`, `properties.params`) and of the host's `autoTrigger` flag. export const entry: SemanticMigration = { id: 'ui-action-group-menu-members-typed', surface: 'page `action:group` and `action:menu` components — each member of `properties.actions` (whose keys ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts index 8752a6f6e0a..c4c5a0cec14 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-block-grouping-config-typed.ts @@ -42,9 +42,10 @@ export const entry: SemanticMigration = { + 'unpadded name silently equivalent, which is the consumer tolerance the contract refuses, ' + 'and a bare string, a number or an empty list has no mapping that says what grouping was ' + 'meant. Metadata AT REST is left exactly as stored — `properties` on a page component is ' - + 'not parsed on the save path, so a stored page keeps loading and renders as it does today; ' - + 'the component-props gate reports such a value as a `component-props-invalid` ' - + 'error at the offending path on `os validate`, `os build` and `os lint`, a padded name ' + + 'not parsed on the metadata load path, so a stored page keeps loading and renders as it does ' + + 'today, and the next save of the page meets the component-props gate at the metadata save ' + + 'door. The gate reports such a value as a `component-props-invalid` error at the offending ' + + 'path on `os validate`, `os build` and `os lint` and at that save door, a padded name ' + 'with the received value and the unpadded name to write. ADR-0049 / ADR-0087.', acceptanceCriteria: 'Every `object-grid` and `object-kanban` node in your pages either omits `grouping` or ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts index 2cf54c516b0..1c393aa0a2f 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts @@ -8,8 +8,9 @@ import type { SemanticMigration } from '../../types.js'; // snake_case. The maintainer ruled on #21704 (fork 2, letter B) that the spec // declares a closed runtime form field of the members the form draws, in // camelCase, keyed by `name`. D3 only: page-component `properties` is not -// parsed on the metadata save or load path, so a stored page is never refused at load; -// and the authored census, with every inline option list evaluated, found no +// parsed on the metadata load path, so a stored page is never refused at load +// (its next save meets the component-props gate at the metadata save door); and +// the authored census, with every inline option list evaluated, found no // working member to respell — the refused values are a type-level test's // `visibleOn` and a fixture pinning that an inline `defaultValue` seeds // nothing. The shipped object-manager dialog's options (`{ label: 'Box', value: diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts index a7264db5f32..ba7ea5b88b2 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts @@ -7,12 +7,13 @@ import type { SemanticMigration } from '../../types.js'; // while the form drew a `{ name }` field entry its own page-builder guide // taught. objectstack-ai/objectui#11550 retired that entry from every authoring // face (the form still draws a STORED one, by its name, as tolerance), so both -// rows now take field names — objectui's own declaration of the member — and -// an object entry is refused with what to write instead. D3 only: page- -// component `properties` is not parsed on the metadata save or load path, so a -// stored page is never refused; and the authored census found no authored -// value to respell — the refused values are fixtures probing the stored read, -// the console warning and objectui's own refusal. +// rows now take field names — objectui's own declaration of the member — and an +// object entry is refused with what to write instead. D3 only: page-component +// `properties` is not parsed on the metadata load path, so a stored page is +// never refused at load (its next save meets the component-props gate at the +// metadata save door); and the authored census found no authored value to +// respell — the refused values are fixtures probing the stored read, the +// console warning and objectui's own refusal. export const entry: SemanticMigration = { id: 'ui-object-form-fields-names-typed', surface: 'page `object-form` and `object-master-detail-form` components — `properties.fields` (whose ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts index 573e0acb556..89b0104f32d 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts @@ -11,8 +11,9 @@ import type { SemanticMigration } from '../../types.js'; // `z.unknown()` (the form draws a `{ name }` field entry and an inline runtime // field inside a section, which the typed shapes would refuse), and // `customFields` waits for the spec to declare objectui's runtime form field. -// D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused at load; an off-shape value has no +// D3 only: page-component `properties` is not parsed on the metadata load path, +// so a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door); an off-shape value has no // rewrite that says what the author meant; and the authored census found no // authored value to respell — the refused values are fixtures probing that the // form refuses them. diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts index c55cf19053d..38288fb63bc 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts @@ -9,8 +9,9 @@ import type { SemanticMigration } from '../../types.js'; // 3, letter B) a page-block section shape of its own — the form view's section // keys plus the three entry arms the form reads, canonical spellings only — and // the stored form view is unchanged. D3 only: page-component `properties` is -// not parsed on the metadata save or load path, so a stored page is never -// refused at load; and the authored census, with every inline option list evaluated, +// not parsed on the metadata load path, so a stored page is never refused at +// load (its next save meets the component-props gate at the metadata save +// door); and the authored census, with every inline option list evaluated, // found no working section to respell — the refused values are the two probes // in objectui's `formSectionGroupReference-7051.test.tsx` (a section declaring // neither `fields` nor `group`, and a group-owned `label` / `collapsible` diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts index 7c2edacd364..f33f1384c47 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts @@ -2,17 +2,18 @@ import type { SemanticMigration } from '../../types.js'; -// #21464 — the `object-gantt` page block's `markers` was `z.array(z.unknown())`: -// its element contract lived only in objectui, so a marker with no `date`, a -// numeric `date` or a misspelled member passed the component-props gate, and -// the chart drew no line, or drew it unlabelled and in the default colour. The -// spec now declares objectui's own authoring declaration of a marker, -// `{ date, label?, color? }` with `date` a string, and the row takes it. D3 -// only: page-component `properties` is not parsed on the metadata save or load -// path, so a stored page is never refused at load; a misspelled member has no rewrite -// that says which of the three the author meant; and the authored census found -// no authored value to respell — the one refused value is objectui's own -// compile-time refusal probe. +// #21464 — the `object-gantt` page block's `markers` was +// `z.array(z.unknown())`: its element contract lived only in objectui, so a +// marker with no `date`, a numeric `date` or a misspelled member passed the +// component-props gate, and the chart drew no line, or drew it unlabelled and +// in the default colour. The spec now declares objectui's own authoring +// declaration of a marker, `{ date, label?, color? }` with `date` a string, and +// the row takes it. D3 only: page-component `properties` is not parsed on the +// metadata load path, so a stored page is never refused at load (its next save +// meets the component-props gate at the metadata save door); a misspelled +// member has no rewrite that says which of the three the author meant; and the +// authored census found no authored value to respell — the one refused value is +// objectui's own compile-time refusal probe. export const entry: SemanticMigration = { id: 'ui-object-gantt-markers-typed', surface: 'page `object-gantt` components — `properties.markers` (whose entries used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts index bb288999ace..7a059230a3e 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts @@ -5,17 +5,18 @@ import type { SemanticMigration } from '../../types.js'; // #21464 — the `object-grid` page block's `columns` was `z.array(z.unknown())` // although the grid reads it with one shape, so a column keyed `accessorKey` / // `header` / `name`, a column with no `field`, a mixed list or a key the grid -// never reads off a column passed the component-props gate, and the grid drew no -// column or ignored the key, in silence. Stage 2 held it because the grid's group -// headers drew a column's `options`, which the list view's column entry does not -// declare; objectui retired that read (the group-header labels come from the -// object field's `options` only), so the member takes the list view's own -// `columns` by reference — the reference it was held from. D3 only: -// page-component `properties` is not parsed on the metadata save or load path, so -// a stored page is never refused at load; a refused column has no rewrite that both keeps -// what the grid draws today and honours what the author wrote; and the authored -// census found no authored value to respell — every refused value is a fixture -// whose refused key the grid does not draw. +// never reads off a column passed the component-props gate, and the grid drew +// no column or ignored the key, in silence. Stage 2 held it because the grid's +// group headers drew a column's `options`, which the list view's column entry +// does not declare; objectui retired that read (the group-header labels come +// from the object field's `options` only), so the member takes the list view's +// own `columns` by reference — the reference it was held from. D3 only: +// page-component `properties` is not parsed on the metadata load path, so a +// stored page is never refused at load (its next save meets the component-props +// gate at the metadata save door); a refused column has no rewrite that both +// keeps what the grid draws today and honours what the author wrote; and the +// authored census found no authored value to respell — every refused value is a +// fixture whose refused key the grid does not draw. export const entry: SemanticMigration = { id: 'ui-object-grid-columns-typed', surface: 'page `object-grid` components — `properties.columns` (whose entries used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts index 9532014e6e7..da8e43ec240 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts @@ -2,16 +2,17 @@ import type { SemanticMigration } from '../../types.js'; -// #21229 — an `object-grid` page block's `exportOptions` was `z.unknown()`, so a -// bare format array (the list view's legacy spelling, which the list view lifts -// to `{ formats }`) was accepted on the grid, whose renderer reads +// #21229 — an `object-grid` page block's `exportOptions` was `z.unknown()`, so +// a bare format array (the list view's legacy spelling, which the list view +// lifts to `{ formats }`) was accepted on the grid, whose renderer reads // `exportOptions.formats` and lifts nothing. The row now takes the list view's // five-member export options OBJECT by identity, not the list view's union. D3 -// only: page-component `properties` is not parsed on the metadata save or load -// path, so a stored page is never refused at load and there is no load-path refusal for -// a conversion to pre-empt; the bare array never worked here, and lifting it -// would change the export menu a deployed grid shows today; the authored census -// found nothing to respell. +// only: page-component `properties` is not parsed on the metadata load path, so +// a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door) and there is no load-path +// refusal for a conversion to pre-empt; the bare array never worked here, and +// lifting it would change the export menu a deployed grid shows today; the +// authored census found nothing to respell. export const entry: SemanticMigration = { id: 'ui-object-grid-export-options-closed', surface: 'page `object-grid` components — `properties.exportOptions` (which used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts index 0881dc7697f..c53c6230497 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts @@ -9,12 +9,12 @@ import type { SemanticMigration } from '../../types.js'; // in silence. The rows now take the list view's own members by reference where // a list view declares one, and the measured shape otherwise. The grid's // `columns` is held at `z.unknown()`: the grid draws a column's `options`, -// which the list view's column entry does not declare. D3 only: -// page-component `properties` is not parsed on the metadata save or load path, -// so a stored page is never refused at load; an off-shape value has no rewrite that -// says what the author meant; and the authored census found no authored value -// to respell — the refused values are fixtures probing that the renderer drops -// them. +// which the list view's column entry does not declare. D3 only: page-component +// `properties` is not parsed on the metadata load path, so a stored page is +// never refused at load (its next save meets the component-props gate at the +// metadata save door); an off-shape value has no rewrite that says what the +// author meant; and the authored census found no authored value to respell — +// the refused values are fixtures probing that the renderer drops them. export const entry: SemanticMigration = { id: 'ui-object-grid-kanban-calendar-list-members-typed', surface: 'page `object-grid` components — `properties.fields`, `.selection`, `.selectable`, ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts index 2408594dba9..11253c37e38 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts @@ -7,7 +7,8 @@ import type { SemanticMigration } from '../../types.js'; // with a fixed shape, so an off-shape value passed every door and the grid // substituted a default or dropped it in silence. The row now takes the shape // each read point takes. D3 only: page-component `properties` is not parsed on -// the metadata save or load path, so a stored page is never refused at load and there is +// the metadata load path, so a stored page is never refused at load (its next +// save meets the component-props gate at the metadata save door) and there is // no load-path refusal for a conversion to pre-empt; an off-shape value has no // rewrite that says what the author meant; and the authored census found // nothing in either repository's corpora to respell. diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts index def3eb0785c..d03c49d6e4b 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts @@ -7,9 +7,10 @@ import type { SemanticMigration } from '../../types.js'; // the list view's member refuses. objectstack-ai/objectui#11522 retired that // dialect from objectui's authoring faces, so the row now takes the list view's // own member, by reference, as `object-grid` does. D3 only: page-component -// `properties` is not parsed on the metadata save or load path, so a stored page -// is never refused at load; and the authored census found no working rule to respell — -// the refused values are objectui's own refusal probes and one no-predicate +// `properties` is not parsed on the metadata load path, so a stored page is +// never refused at load (its next save meets the component-props gate at the +// metadata save door); and the authored census found no working rule to respell +// — the refused values are objectui's own refusal probes and one no-predicate // fixture in this package, respelled in the same change. export const entry: SemanticMigration = { id: 'ui-object-kanban-conditional-formatting-typed', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts index d0a694e6b28..559bda03679 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts @@ -9,8 +9,9 @@ import type { SemanticMigration } from '../../types.js'; // the record page in silence. The three rows now take that schema by reference, // the carrier `object-grid`, `object-kanban`, `object-calendar` and // `object-timeline` already take. D3 only: page-component `properties` is not -// parsed on the metadata save or load path, so a stored page is never refused at load; -// an off-shape value has no rewrite that says what the author meant; and the +// parsed on the metadata load path, so a stored page is never refused at load +// (its next save meets the component-props gate at the metadata save door); an +// off-shape value has no rewrite that says what the author meant; and the // authored census found nothing in either repository's corpora to respell. export const entry: SemanticMigration = { id: 'ui-object-map-gantt-tree-navigation-typed', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts index 1d7690f71cf..1e6de445c86 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts @@ -7,9 +7,10 @@ import type { SemanticMigration } from '../../types.js'; // while the other two carriers — a relationship field's `inlineColumns` and a // form view's `subforms[].columns` (#20901) — were the strict // `InlineGridColumnSchema`; the entry is now a strict shape of the twelve keys -// objectui's `MasterDetailForm` reads, and its `columns` REFERENCES that schema. -// D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused at load and there is no load-path +// objectui's `MasterDetailForm` reads, and its `columns` REFERENCES that +// schema. D3 only: page-component `properties` is not parsed on the metadata +// load path, so a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door) and there is no load-path // refusal for a conversion to pre-empt; the authored census found no `field` // spelling to respell. `defineStack`'s identity-only check reaches this carrier // too, the reach `inline-grid-column-identity-only-currency-scale-refused` diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts index 77cdaf21ac6..edb9a661c96 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts @@ -5,18 +5,20 @@ import type { SemanticMigration } from '../../types.js'; // #21464 — two members of the `object-metric` page block were `z.unknown()` // although the tile reads each with a fixed shape, so an off-shape value passed // the component-props gate and the tile asked the server for a measure it could -// not answer or drew an empty badge, in silence. `aggregate` now takes the query -// AST's aggregation functions and the chart aggregate's `groupBy` union by -// reference (`groupBy` optional: a metric is one number), and `trend` the -// badge's measured shape. `drillDown` and `compareTo` are held at `z.unknown()`: each -// by-reference candidate disagrees with the tile's read (the chart's drill-down -// declares `filter`, which the tile never reads, and refuses `report`, which it -// draws; the dashboard widget's comparison declares `dimension`, never read on -// this path), so each waits on a ruling. D3 only: page-component `properties` -// is not parsed on the metadata save or load path, so a stored page is never -// refused at load; an off-shape value has no rewrite that says what the author meant; -// and the authored census found no authored value to respell — every refused -// value is a fixture probing that the tile does not draw it. +// not answer or drew an empty badge, in silence. `aggregate` now takes the +// query AST's aggregation functions and the chart aggregate's `groupBy` union +// by reference (`groupBy` optional: a metric is one number), and `trend` the +// badge's measured shape. `drillDown` and `compareTo` are held at +// `z.unknown()`: each by-reference candidate disagrees with the tile's read +// (the chart's drill-down declares `filter`, which the tile never reads, and +// refuses `report`, which it draws; the dashboard widget's comparison declares +// `dimension`, never read on this path), so each waits on a ruling. D3 only: +// page-component `properties` is not parsed on the metadata load path, so a +// stored page is never refused at load (its next save meets the component-props +// gate at the metadata save door); an off-shape value has no rewrite that says +// what the author meant; and the authored census found no authored value to +// respell — every refused value is a fixture probing that the tile does not +// draw it. export const entry: SemanticMigration = { id: 'ui-object-metric-aggregate-trend-typed', surface: 'page `object-metric` components — `properties.aggregate` and `.trend` (which used to accept any ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts index 47eeb7fa1a9..9d0f108a466 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts @@ -6,13 +6,14 @@ import type { SemanticMigration } from '../../types.js'; // although the tile reads it with one shape, so a bare kind string, a kind // outside the two or a `dimension` passed the component-props gate, and the // tile compared against the previous period, or shifted its own filter's window -// rather than the dimension named, in silence. It now takes the tile's read: -// `{ kind }`, with `kind` the dashboard widget comparison's own vocabulary by +// rather than the dimension named, in silence. It now takes the tile's read: `{ +// kind }`, with `kind` the dashboard widget comparison's own vocabulary by // reference, and `dimension` refused by name with a prescription (the inline // tile shifts the date macros in its own `filter` and never reads a dataset // time dimension). D3 only: page-component `properties` is not parsed on the -// metadata save or load path, so a stored page is never refused at load; a `dimension` -// has no rewrite that keeps the window the author meant; and the authored census +// metadata load path, so a stored page is never refused at load (its next save +// meets the component-props gate at the metadata save door); a `dimension` has +// no rewrite that keeps the window the author meant; and the authored census // found no authored value to respell — the one refused value is a fixture // probing that the tile does not read `dimension`. export const entry: SemanticMigration = { diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts index 94b04c57727..d3f5889f554 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts @@ -2,16 +2,17 @@ import type { SemanticMigration } from '../../types.js'; -// #21464 — the `object-metric` page block's `drillDown.report` was `z.unknown()`: -// the tile hands it to the shared drill drawer, which draws a dataset-bound -// report and lists the records for any other value, and no spec drill shape -// declared it. The maintainer ruled on #21704 (fork 1, letter B) that it is -// `ReportSchema` by reference, once a joined report refuses a block that binds -// no dataset (#21702), so a report the member admits is one the drawer draws. -// D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused at load; and the authored census found -// no drawn report to respell — the refused values are objectui's probes of the -// values the drawer does NOT draw. +// #21464 — the `object-metric` page block's `drillDown.report` was +// `z.unknown()`: the tile hands it to the shared drill drawer, which draws a +// dataset-bound report and lists the records for any other value, and no spec +// drill shape declared it. The maintainer ruled on #21704 (fork 1, letter B) +// that it is `ReportSchema` by reference, once a joined report refuses a block +// that binds no dataset (#21702), so a report the member admits is one the +// drawer draws. D3 only: page-component `properties` is not parsed on the +// metadata load path, so a stored page is never refused at load (its next save +// meets the component-props gate at the metadata save door); and the authored +// census found no drawn report to respell — the refused values are objectui's +// probes of the values the drawer does NOT draw. export const entry: SemanticMigration = { id: 'ui-object-metric-drill-down-report-typed', surface: 'page `object-metric` components — `properties.drillDown.report` (which used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts index 3c6c608b327..534e6164fa8 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts @@ -6,16 +6,18 @@ import type { SemanticMigration } from '../../types.js'; // although the tile reads it with one shape, so a drill `filter`, a `mode`, a // misspelled member or a non-numeric page size passed the component-props gate // and the tile ignored each, in silence. It now takes the tile's read: the five -// list members (`enabled`, `title`, `target`, `columns`, `maxRows`) are the chart -// drill-down's own by reference, and `filter` and `mode` are refused by name with -// the prescriptions the renderer's own type for this block carries (a metric has -// no click event for a drill filter to resolve against, and no row for `mode` to -// open). The drill `report` is held open, not typed: the tile draws a -// dataset-bound report, but the spec declares no drill report yet. D3 only: -// page-component `properties` is not parsed on the metadata save or load path, so -// a stored page is never refused at load; a drill `filter` has no rewrite that keeps the -// scope the author meant; and the authored census found no authored value to -// respell — the two refused values are fixtures probing the refusal. +// list members (`enabled`, `title`, `target`, `columns`, `maxRows`) are the +// chart drill-down's own by reference, and `filter` and `mode` are refused by +// name with the prescriptions the renderer's own type for this block carries (a +// metric has no click event for a drill filter to resolve against, and no row +// for `mode` to open). The drill `report` is held open, not typed: the tile +// draws a dataset-bound report, but the spec declares no drill report yet. D3 +// only: page-component `properties` is not parsed on the metadata load path, so +// a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door); a drill `filter` has no +// rewrite that keeps the scope the author meant; and the authored census found +// no authored value to respell — the two refused values are fixtures probing +// the refusal. export const entry: SemanticMigration = { id: 'ui-object-metric-drill-down-typed', surface: 'page `object-metric` components — `properties.drillDown` (which used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts index 53d3ba9cf02..e805d311924 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts @@ -2,17 +2,18 @@ import type { SemanticMigration } from '../../types.js'; -// #21464 — the `object-timeline` page block's `items` was `z.array(z.unknown())`: -// each entry is objectui's authored timeline element (`TimelineFeedItem` / -// `TimelineGanttItem`, ruled on objectui#6356), which the spec did not declare, -// and the arm an entry must be is chosen by the row's `variant`. The maintainer -// ruled on #21704 (fork 4, letter B): both arms closed, a feed entry's `content` -// opaque, a row refinement pairing each entry with the arm `variant` selects, -// and a gantt bar's dates a string or a number. D3 only: page-component -// `properties` is not parsed on the metadata save or load path, so a stored page -// is never refused at load; and the authored census found no drawn entry to respell — -// the refused values are objectui's probes of its render-time gantt date -// diagnostic. +// #21464 — the `object-timeline` page block's `items` was +// `z.array(z.unknown())`: each entry is objectui's authored timeline element +// (`TimelineFeedItem` / `TimelineGanttItem`, ruled on objectui#6356), which the +// spec did not declare, and the arm an entry must be is chosen by the row's +// `variant`. The maintainer ruled on #21704 (fork 4, letter B): both arms +// closed, a feed entry's `content` opaque, a row refinement pairing each entry +// with the arm `variant` selects, and a gantt bar's dates a string or a number. +// D3 only: page-component `properties` is not parsed on the metadata load path, +// so a stored page is never refused at load (its next save meets the +// component-props gate at the metadata save door); and the authored census +// found no drawn entry to respell — the refused values are objectui's probes of +// its render-time gantt date diagnostic. export const entry: SemanticMigration = { id: 'ui-object-timeline-items-typed', surface: 'page `object-timeline` components — `properties.items` (whose entries used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts index 3a1ba81bce7..b3eedc524f2 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts @@ -8,9 +8,10 @@ import type { SemanticMigration } from '../../types.js'; // component-props gate, and the rail bound nothing for it and drew the default // field. The spec now declares objectui's own declaration of the binding // record, four optional field names, and the row takes it. D3 only: page- -// component `properties` is not parsed on the metadata save or load path, so a -// stored page is never refused; and the authored census found no refused -// authored value — every one parses. +// component `properties` is not parsed on the metadata load path, so a stored +// page is never refused at load (its next save meets the component-props gate +// at the metadata save door); and the authored census found no refused authored +// value — every one parses. export const entry: SemanticMigration = { id: 'ui-object-timeline-mapping-typed', surface: 'page `object-timeline` components — `properties.mapping` (which used to accept any value)', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts index a4213034b01..a35aaa8df29 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-operation-declared.ts @@ -20,9 +20,11 @@ export const entry: SemanticMigration = { + 'beside `operation: \'update\'`: a row with an `api` or `script` executor, the write and a hop parsed, while ' + 'the same action was refused. The rows now declare `ActionSchema`\'s own `operation` enum and refuse the ' + 'pairing with the action\'s own text: the hop would follow the write, which runs no handler and no executor, ' - + 'so its success carries no result `${result.*}` could read. A page\'s `properties` is not parsed on the ' - + 'metadata save or load path; `os validate`\'s props gate parses it against these same rows and reports the ' - + 'refusal at the row\'s `operation` or `onSuccess`. Measured producers: none in this repository; none in ' + + 'so its success carries no result `${result.*}` could read. The component-props gate parses a page\'s ' + + '`properties` against these same rows and reports the refusal at the row\'s `operation` or `onSuccess` on ' + + '`os validate`, `os build` and `os lint`. A stored page still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the ' + + 'component-props gate at the metadata save door. Measured producers: none in this repository; none in ' + 'objectui\'s action rows at its pin or main.', acceptanceCriteria: 'Every page action row\'s `operation` is absent or `\'update\'`, and no row with `operation: \'update\'` carries ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts index 2290f99977e..0b2840e0736 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts @@ -3,14 +3,15 @@ import type { SemanticMigration } from '../../types.js'; // #21142 — the fourth carrier of the inline grid column, and the last -// registered `record:*` renderer without a `ComponentPropsMap` row. The type sat -// on the string-arm registration ledger instead, so the component-props gate -// skipped its props bag: the showcase project page keyed all five of its +// registered `record:*` renderer without a `ComponentPropsMap` row. The type +// sat on the string-arm registration ledger instead, so the component-props +// gate skipped its props bag: the showcase project page keyed all five of its // columns `field` and published green over a grid of empty cells. The row // declares the fifteen keys objectui's `LineItemsPanel` reads, and its // `columns` REFERENCES `InlineGridColumnSchema`. D3 only: page-component -// `properties` is not parsed on the metadata save or load path, so a stored -// page is never refused at load and there is no load-path refusal for a conversion to +// `properties` is not parsed on the metadata load path, so a stored page is +// never refused at load (its next save meets the component-props gate at the +// metadata save door) and there is no load-path refusal for a conversion to // pre-empt; the one `field`-keyed producer the census found is respelled in the // same change. export const entry: SemanticMigration = { diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index edb65654b25..13b3193afc2 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -5225,10 +5225,11 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * own retirement text) and a key the block does not declare. * * No conversion lifts the array: it never worked on this surface. Read by the - * component-props gate (refused as an error); a stored page still saves and - * loads, because page-component `properties` is not parsed on the metadata - * save or load path (the `ui-object-grid-export-options-closed` semantic - * entry). + * component-props gate (refused as an error) on `os validate`, `os build` + * and `os lint` and at the metadata save door. A stored page still loads, + * because page-component `properties` is not parsed on the metadata load + * path, and its next save meets the component-props gate at the save door + * (the `ui-object-grid-export-options-closed` semantic entry). */ exportOptions: ListViewExportOptionsSchema.optional() .describe("Export config — the object `{ formats?, maxRecords?, includeHeaders?, fileNamePrefix?, streaming? }`, the same block a list view's `exportOptions` declares, with `formats` drawn from `csv`, `xlsx` and `json`. A bare format array is refused: the grid reads `exportOptions.formats`, so write `{ formats: ['csv', 'xlsx'] }`"),