Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/10872-flat-arm-responsive-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
- The tolerant face (`safeValidateSchema`, the face `objectui validate` runs) used to keep any node-level `responsiveStyles` value on these arms unjudged. It now refuses a value the spec refuses, at `responsiveStyles`, with the spec's own issue: a breakpoint the spec does not have (`md`), a number instead of a map, or a style value that is neither a string nor a number. `SchemaRenderer` applied nothing for a number or for a map with no spec breakpoint. It emitted a non-CSS value inside a valid breakpoint verbatim, where the browser dropped it, and it applied the valid breakpoints of a mixed map.
- In TypeScript, a `responsiveStyles` value that is not the spec's `ResponsiveStyles` no longer type-checks on these three interfaces.
- An `object-view`'s `table` slot, which takes `ObjectGridSchema`'s members, does not gain the key. `ObjectView` draws its grid as a component, not as a schema node, so nothing compiles a `responsiveStyles` map written in `table`. The slot therefore refuses `table.responsiveStyles` by name, with that reason, on both faces, as it refuses the other node-level keys (objectui#10976). The tolerant face used to keep it unjudged, and the TypeScript slot never declared it.

⚠️ **Dated note, 2026-10-01 — `object-chart` declares `dataSource` — objectui#11070.** "`dataSource` on `flex` and `object-chart`" in the list of node-level keys that stay undeclared held when this change landed. Later in this same release objectui#11070 (round 7) declared `dataSource` on `object-chart`, as the spec's `ElementDataSourceSchema` by reference; `flex` is unchanged. `.changeset/11070-grid-columns-chart-binding-round7.md` states what ships; the text above is kept as the reading of this change.
23 changes: 23 additions & 0 deletions .changeset/11070-grid-columns-chart-binding-round7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@object-ui/types': minor
'@object-ui/fields': minor
---

The grid field's `columns` is `@objectstack/spec`'s inline grid column list, by reference, and `object-chart` declares the per-element `dataSource` binding like the other gate-wrapped blocks (objectui#11070, round 7).

- **Grid columns (`@object-ui/types`).** `GridFieldMetadata.columns` and the form-field face's `FormField.columns` (TypeScript and the zod mirror `FormFieldSchema`) are `FieldSchema.inlineColumns` by reference: an array of the spec's strict, `name`-keyed inline grid column (`InlineGridColumn`), which the spec declares as the mirror of the `grid` widget's column. The key stays `columns`: a `grid` field is objectui's own field type, and the spec spells the same list `inlineColumns` on a `master_detail` field.
- **`GridColumnDefinition` is retired (`@object-ui/types`).** Nothing read it, and it was not the shape the widget read: it required a free-form `type` and declared `defaultValue` and `validate`, which no reader consumed. Use `InlineGridColumn` from `@objectstack/spec/data`, or `NonNullable<GridFieldMetadata['columns']>[number]`.
- **`GridColumn` (`@object-ui/fields`)** is `InlineGridColumn` by reference instead of a hand-written copy. `GridField` already read each column by exactly the spec's keys (twenty, with no second spelling), so nothing changes at render time.
- **`object-chart` · `dataSource` (`@object-ui/types`).** `ObjectChartSchema` (TypeScript and the zod mirror) and the authored arm `ObjectChartBlockSchema` declare `dataSource` as the spec's `ElementDataSourceSchema`, by reference, at node level beside the `properties` bag, as `ObjectFormBlockSchema` and `ObjectMapBlockSchema` do. The registration is gate-wrapped, so `ElementDataSourceGate` reads the binding off the node and lands its `object` on `objectName`; the react-page wrapper no longer writes the host adapter under that key (objectui#11070, round 2).

**Clause-②: yes (narrowing).** The strict face's accept set widens: a grid field's `columns` and an `object-chart` node's `dataSource` binding used to be refused by name there and now parse. The tolerant face narrows, because both keys are now judged by their declared type on both faces:

- a grid column the spec refuses is refused: the retired `field` spelling, a `title`, a per-column `defaultValue`, a `type` outside the nine cell controls (`text`, `number`, `currency`, `date`, `datetime`, `time`, `select`, `lookup`, `file`), or a `scale` on a column declaring `type: 'currency'`. `FormFieldSchema` strips an undeclared key, so before this change such a column list was dropped from the parsed field in silence;
- an `object-chart` node whose `dataSource` is not a binding is refused, `null` or an adapter object included. The authored arm is `.passthrough()`, so before this change that value was kept unjudged.

## ⚠️ BREAKING, priced as minor under the fixed group's version policy

- **TypeScript.** `import type { GridColumnDefinition } from '@object-ui/types'` no longer resolves. A grid column literal typed `GridFieldMetadata['columns']`, `FormField['columns']` or `GridColumn` that carries a key the spec column does not declare, or a `type` outside the nine, is a compile error. A `readonlyWhen` / `requiredWhen` written as an object must name its `dialect`, as the spec's expression envelope does. An `ObjectChartSchema` literal whose `dataSource` holds an adapter is a compile error; pass the adapter as the component's `dataSource` prop, or through `SchemaRendererProvider`.
- **Validation.** The documents above that the tolerant face (`safeValidateSchema`, and so `objectui validate`) accepted are refused. Fix: write the spec's column keys (`name`, `label`, `type`, …), or drop a key the column does not have; write a binding (`{ "object": "…" }`) or no `dataSource` on an `object-chart` node.

Rendering does not change: `GridField` reads the same keys as before, and `ElementDataSourceGate` ignores a value that is not a binding.
2 changes: 2 additions & 0 deletions .changeset/11070-strict-face-read-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ The strict authoring face accepts keys a registered renderer reads, which it use
**What now refuses that did not.** A declared key is judged by its declared type on BOTH faces. On `FormFieldSchema`, which strips undeclared keys, a wrong-typed value for one of the ten field keys used to be dropped silently and is now refused (for example `accept: "application/pdf"`, since the spec types `accept` as an array, or `rows: 0`). On the passthrough nodes, a wrong-typed `showSubmit` or `dataSource` used to be kept unjudged and is now refused. That includes a `dataSource` holding the host's adapter object or `null`, which is what the react-page wrapper writes on the nodes it builds in memory: those nodes are rendered, not validated, and `ElementDataSourceGate` ignores a value that is not a binding, so their rendering does not change. An `object-view`'s `table` slot now refuses `dataSource` by name, like the other record sources the view owns (`data`, `staticData`, `bind`), because the view does not hand it to its grid; its `form` slot carries `dataSource` as it carries `bind` and `data`. On the TypeScript face these keys are now typed members rather than the `[key: string]: any` index signature, so a wrong-typed value is a compile error. Measured over the schema catalog, the docs JSON fences and the apps' authored documents, at this change's base (`88fbd793d`) and on this change: the tolerant face refused the same documents both times.

**What is deliberately NOT declared.** The legacy spellings `reference_to` and `min_length` stay refused on the strict face; write the spec's `reference` and `minLength`. `return_type`, `summary_type`, the grid field's `columns`, `object-chart`'s `dataSource` and the dashboard widget keys stay refused until objectui#11070 settles them.

⚠️ **Dated note, 2026-10-01 — the grid field's `columns` and `object-chart`'s `dataSource` are settled — objectui#11070.** "the grid field's `columns`, `object-chart`'s `dataSource` … stay refused until objectui#11070 settles them" above held when this change landed. Later in this same release round 7 of objectui#11070 declared both: `columns` as the spec's `FieldSchema.inlineColumns` and the binding as the spec's `ElementDataSourceSchema`, each by reference. `.changeset/11070-grid-columns-chart-binding-round7.md` states what ships; the text above is kept as the reading of this change.
2 changes: 2 additions & 0 deletions .changeset/11276-object-chart-properties-bag.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ Nothing changes at render time: `SchemaRenderer` hoists every `properties` key o
**The same release's `.changeset/10518-object-chart-y-axis-declared.md`, `.changeset/10608-object-chart-legacy-axis-keys-retired.md`, `.changeset/10770-object-chart-react-tier-node.md` and `.changeset/10872-flat-arm-responsive-styles.md`** describe what the validators say about an `object-chart` written flat. On an authored node those readings now happen in the bag: a malformed `xAxis` is refused at `properties.xAxis`, a retired key at `properties.KEY`, a missing family at `properties.chartType`; `responsiveStyles` stays node-level and is judged there as they say. The flat mirror they name still behaves as they say.

**What did not move.** The TypeScript `ObjectChartSchema` and its zod mirror `ObjectChartSchema` stay published and unchanged in shape. They are the node as `ObjectChart` reads it after the hoist, and as code composes it or hands it to `<ObjectChart schema={…}>` directly.

⚠️ **Dated note, 2026-10-01 — `dataSource` is declared on this node — objectui#11070.** "`dataSource` stays undeclared on this node, as objectui#11070 left it" above held when this change landed. Later in this same release objectui#11070 (round 7) declared it on `ObjectChartSchema` and on `ObjectChartBlockSchema`, at node level beside the bag, as the spec's `ElementDataSourceSchema` by reference: a binding parses on both faces, and a `dataSource` that is not a binding is refused. `.changeset/11070-grid-columns-chart-binding-round7.md` states what ships; the text above is kept as the reading of this change.
2 changes: 2 additions & 0 deletions .changeset/6138-fields-schema-block-parity-pr2.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@ The gate's blocks-to-compile count rises from 248 to 249 — 21 conversions are
one-block-for-one-block and `lookup.mdx` becomes two blocks (data-source-backed
and static-option) — with diagnostics at 0, no new `FRAGMENT_MARKER`
declarations, and the declared-fragment count unmoved at 111.

⚠️ **Dated note, 2026-10-01 — `GridColumnDefinition` is retired — objectui#11070.** The two mentions of `GridColumnDefinition` above held when this change landed. Later in this same release objectui#11070 (round 7) retired it: `GridFieldMetadata.columns` is `@objectstack/spec`'s `FieldSchema.inlineColumns` by reference, and `grid.mdx` annotates against that. The spec's inline grid column is closed, so the page still cannot teach a column key the type does not have, and it declares neither a column `editable` nor a string `width`. `.changeset/11070-grid-columns-chart-binding-round7.md` states what ships; the text above is kept as the reading of this change.
2 changes: 2 additions & 0 deletions .changeset/8209-datetime-widget-faces.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ stored string for an unreadable value, unchanged (objectui#3569).

With this, objectui#7443's "`datetime` has one home" holds for all six bare
no-bag sites objectui#8194 enumerated.

⚠️ **Dated note, 2026-10-01 — `GridColumn` is the spec's inline grid column — objectui#11070.** "(`GridColumn`, mirroring the published `GridColumnDefinition`)" above held when this change landed. Later in this same release objectui#11070 (round 7) retired `GridColumnDefinition` and made `GridColumn` `@objectstack/spec`'s `InlineGridColumn` by reference. That shape declares no `format` key either, so the reasoning above is unchanged. `.changeset/11070-grid-columns-chart-binding-round7.md` states what ships; the text above is kept as the reading of this change.
33 changes: 17 additions & 16 deletions content/docs/fields/grid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ The Grid Field component provides an inline table for managing related records o

A grid field is authored as `GridFieldMetadata` (`@object-ui/types`), which is the
source of truth for the key set: it extends `BaseFieldMetadata` with the column list
and the row-count and row-action limits. Each column is a `GridColumnDefinition`, so
the columns are checked by the same compiler that checks the field.
and the row-count and row-action limits. Each column is `@objectstack/spec`'s inline
grid column (`InlineGridColumn`, the element of the spec's `inlineColumns` list on a
`master_detail` field), typed by reference, so the columns are checked by the same
compiler that checks the field, and `objectui validate` refuses a column key the spec
does not declare.

```ts
import type { GridFieldMetadata } from '@object-ui/types';
Expand All @@ -33,7 +36,7 @@ const lineItems: GridFieldMetadata = {
label: 'Line Items',
columns: [
{ name: 'product', label: 'Product', type: 'lookup', required: true, width: 240 },
{ name: 'quantity', label: 'Qty', type: 'number', defaultValue: 1, width: 80 },
{ name: 'quantity', label: 'Qty', type: 'number', width: 80 },
{ name: 'unit_price', label: 'Unit Price', type: 'currency', width: 120 },
],
min_rows: 1,
Expand All @@ -44,15 +47,18 @@ const lineItems: GridFieldMetadata = {
};
```

A column's `width` is a **number** of pixels, and there is no per-column `editable`
key: whether cells can be edited follows the field's own read-only state.
A column's `width` is a **number** of pixels. There is no per-column `editable`
key (whether cells can be edited follows the field's own read-only state) and no
per-column `defaultValue`: a new row starts with every cell empty.

The value being edited, and the `className` / `disabled` a host supplies, are **not**
metadata keys — they are runtime widget props. See [Field Widget Props](/docs/fields/widget-props).

## Column Types

Columns can use any field type:
A column's `type` is one of the spec's nine cell controls: `text`, `number`,
`currency`, `date`, `datetime`, `time`, `select`, `lookup` and `file`. Any other
value is refused.

```plaintext
columns: [
Expand All @@ -61,7 +67,6 @@ columns: [
{ name: 'price', label: 'Price', type: 'currency' },
{ name: 'date', label: 'Date', type: 'date' },
{ name: 'status', label: 'Status', type: 'select', options: [...] },
{ name: 'active', label: 'Active', type: 'boolean' },
{ name: 'receipt', label: 'Receipt', type: 'file', accept: ['image/*', '.pdf'] }
]
```
Expand Down Expand Up @@ -134,7 +139,7 @@ const gridValue = [
{ name: 'product', label: 'Product', type: 'lookup', reference: 'products' },
{ name: 'quantity', label: 'Qty', type: 'number' },
{ name: 'price', label: 'Price', type: 'currency' },
{ name: 'discount', label: 'Discount', type: 'percent' },
{ name: 'discount', label: 'Discount', type: 'number' },
{ name: 'total', label: 'Total', type: 'currency' }
]
}
Expand All @@ -149,9 +154,9 @@ const gridValue = [
label: 'Tasks',
columns: [
{ name: 'task', label: 'Task', type: 'text', required: true },
{ name: 'assigned_to', label: 'Assigned To', type: 'user' },
{ name: 'assigned_to', label: 'Assigned To', type: 'lookup', reference: 'sys_user' },
{ name: 'due_date', label: 'Due Date', type: 'date' },
{ name: 'completed', label: 'Done', type: 'boolean' }
{ name: 'completed', label: 'Done', type: 'select', options: [{ label: 'Yes', value: 'true' }, { label: 'No', value: 'false' }] }
]
}
```
Expand Down Expand Up @@ -248,7 +253,8 @@ await db.insert('orders', order);
Example validation for grid data:

```plaintext
const validateGridData = (data: any[], columns: ColumnDefinition[]) => {
// InlineGridColumn: the spec's grid column type (@objectstack/spec/data)
const validateGridData = (data: any[], columns: InlineGridColumn[]) => {
const errors: string[] = [];

data.forEach((row, index) => {
Expand All @@ -262,11 +268,6 @@ const validateGridData = (data: any[], columns: ColumnDefinition[]) => {
if (col.type === 'number' && isNaN(row[col.name])) {
errors.push(`Row ${index + 1}: ${col.label} must be a number`);
}

// Check min/max
if (col.min !== undefined && row[col.name] < col.min) {
errors.push(`Row ${index + 1}: ${col.label} must be >= ${col.min}`);
}
});
});

Expand Down
20 changes: 11 additions & 9 deletions packages/fields/src/widgets/GridField.declaredSpelling.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

/**
* objectui#3951 — grid columns have ONE key spelling, and it is the declared
* one: `GridColumnDefinition.name` (`@object-ui/types`), the same key the grid
* docs page and the three `fields-grid` catalog examples author.
* one: `name`, the key of `GridFieldMetadata['columns']` (`@object-ui/types`),
* which is `@objectstack/spec`'s inline grid column by reference since
* objectui#11070 — the same key the grid docs page and the three `fields-grid`
* catalog examples author.
*
* `GridField` used to declare its own local column interface keyed by `field`
* and read `c.field` everywhere — `key={c.field}`, `row[c.field]`,
Expand All @@ -19,9 +21,9 @@
* column (the key was `undefined`). The three demos on `/docs/fields/grid`
* shipped in exactly that state.
*
* The fixtures below are typed as `GridColumnDefinition[]` on purpose: the pin
* is anchored to the DECLARED contract, not to the widget's own idea of it, so
* the two can never silently drift apart again. Per AGENTS.md #0.1 the fix is
* The fixtures below are typed as `GridFieldMetadata['columns']` on purpose:
* the pin is anchored to the DECLARED contract, not to the widget's own idea
* of it, so the two can never silently drift apart again. Per AGENTS.md #0.1 the fix is
* one spelling at the producer — there is deliberately no `c.field ?? c.name`
* alias to make the retired spelling keep working.
*
Expand All @@ -37,11 +39,11 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import type { GridColumnDefinition, GridFieldMetadata } from '@object-ui/types';
import type { GridFieldMetadata } from '@object-ui/types';
import { GridField } from './GridField';

/** Authored exactly as `GridColumnDefinition` declares — keyed by `name`. */
const columns: GridColumnDefinition[] = [
/** Authored exactly as `GridFieldMetadata['columns']` declares — keyed by `name`. */
const columns: NonNullable<GridFieldMetadata['columns']> = [
{ name: 'product', label: 'Product', type: 'text' },
{ name: 'quantity', label: 'Qty', type: 'number' },
{ name: 'price', label: 'Price', type: 'currency' },
Expand All @@ -56,7 +58,7 @@ const rows = [
const field = { type: 'grid', name: 'order_items', columns } as GridFieldMetadata;

describe('GridField reads the DECLARED column spelling (objectui#3951)', () => {
it('renders every cell populated from metadata authored as GridColumnDefinition', () => {
it('renders every cell populated from metadata authored as the declared column type', () => {
render(<GridField value={rows} onChange={() => {}} field={field} />);

// One cell input per column per row, each echoing the row's stored value —
Expand Down
6 changes: 3 additions & 3 deletions packages/fields/src/widgets/GridField.keyWarning.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/**
* objectui#3951, diagnostics half — a grid authored in the declared spelling
* (`GridColumnDefinition.name`) must render without React's missing-key
* (`name`, the key of `GridFieldMetadata['columns']`) must render without React's missing-key
* warning. While `GridField` read a divergent `field` key, every header, chip
* and cell was emitted with `key={undefined}`, so a spec-compliant grid logged
* the warning on top of rendering blank cells.
Expand All @@ -30,10 +30,10 @@
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import React from 'react';
import type { GridColumnDefinition, GridFieldMetadata } from '@object-ui/types';
import type { GridFieldMetadata } from '@object-ui/types';
import { GridField } from './GridField';

const columns: GridColumnDefinition[] = [
const columns: NonNullable<GridFieldMetadata['columns']> = [
{ name: 'product', label: 'Product', type: 'text' },
{ name: 'quantity', label: 'Qty', type: 'number' },
{ name: 'price', label: 'Price', type: 'currency' },
Expand Down
Loading
Loading