Skip to content

Commit d3a5b3a

Browse files
committed
fix(spec): hold object-grid columns at z.unknown(): the grid draws an authored column's options, which ListColumn does not declare (#21464 stage 2)
Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
1 parent 91fafb2 commit d3a5b3a

6 files changed

Lines changed: 101 additions & 112 deletions

‎.changeset/21464-component-props-list-family-typed.md‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
'@objectstack/spec': minor
33
---
44

5-
feat(spec)!: the list members of an `object-grid`, `object-kanban` or `object-calendar` page block take the shape the block reads instead of any value — the grid's `columns`, `fields`, `selection`, `selectable`, `rowActions`, `bulkActions` and `batchActions`, the kanban's `columns` and the calendar's `calendar` (#21464)
5+
feat(spec)!: eight list members of an `object-grid`, `object-kanban` or `object-calendar` page block take the shape the block reads instead of any value — the grid's `fields`, `selection`, `selectable`, `rowActions`, `bulkActions` and `batchActions`, the kanban's `columns` and the calendar's `calendar` (#21464)
66

77
Clause-②: yes (narrowing)
88

@@ -12,20 +12,17 @@ Clause-②: yes (narrowing)
1212

1313
**`@objectstack/spec`**
1414

15-
- **Nine members are typed.** `ComponentPropsMap['object-grid']`, `['object-kanban']` and `['object-calendar']` declared these members as `z.unknown()` (an array of it for the lists), although each renderer reads them with one shape. Any value passed, and an off-shape one was dropped or substituted with no report: a grid column keyed `accessorKey` or `name`, or a column list mixing strings and objects, drew no column; an object entry in `fields` named no field; a `{ name }` entry in `bulkActions` was skipped; a kanban lane list mixing objects and strings drew a blank lane; a calendar block with no `startDateField` placed no event.
16-
- **The list view's own members, by reference**, where a list view declares one: the grid's `columns` (all field-name strings, or all column entries `{ field, label?, width?, … }`), `selection` (`{ type }`, with `none`, `single` or `multiple`), `rowActions` and `bulkActions` (action-name strings), and the calendar's `calendar` (`{ startDateField, endDateField?, titleField?, colorField?, allDayField? }`). `batchActions`, the second spelling of `bulkActions` that the grid reads first, takes `bulkActions`'s def. Neither spelling is retired here.
15+
- **Eight members are typed.** `ComponentPropsMap['object-grid']`, `['object-kanban']` and `['object-calendar']` declared these members as `z.unknown()` (an array of it for the lists), although each renderer reads them with one shape. Any value passed, and an off-shape one was dropped or substituted with no report: an object entry in `fields` named no field; a `{ name }` entry in `bulkActions` was skipped; a kanban lane list mixing objects and strings drew a blank lane; a calendar block with no `startDateField` placed no event.
16+
- **The list view's own members, by reference**, where a list view declares one: the grid's `selection` (`{ type }`, with `none`, `single` or `multiple`), `rowActions` and `bulkActions` (action-name strings), and the calendar's `calendar` (`{ startDateField, endDateField?, titleField?, colorField?, allDayField? }`). `batchActions`, the second spelling of `bulkActions` that the grid reads first, takes `bulkActions`'s def. Neither spelling is retired here.
1717
- **The measured shape**, where no list view declares the member: the grid's `fields` (field-name strings), the grid's `selectable` (`true`, `false`, `'single'` or `'multiple'`), and the kanban's `columns` (all lanes `{ id, title, cards?, limit?, className?, collapsed? }`, or all bare value strings). A lane `id` and `title` are strings, a static card carries a string `id` and `title` beside its row's own values, and `limit` is a positive integer.
18-
- **`ObjectGridProps`, `ObjectKanbanProps` and `ObjectCalendarProps`** (and their `…Parsed` twins) carry these types on the nine members instead of `unknown`.
19-
- **The enumeration pin** loses the nine lines, and the list-family stage is done. One `z.unknown()` member is added and recorded: the rest of a static kanban card (its row's own values, beside the typed `id` and `title`).
18+
- **`ObjectGridProps`, `ObjectKanbanProps` and `ObjectCalendarProps`** (and their `…Parsed` twins) carry these types on the eight members instead of `unknown`.
19+
- **The grid's `columns` is not narrowed** and still accepts any value. The list view's column entry is its by-reference shape, and the grid's draw path reads exactly that, but the grid's group headers also draw the labels from an authored column's `options` (the column whose `field` is the grouping field, ahead of the field's own options). The list view's column entry declares no `options`, so typing `columns` now would refuse a value the grid draws. It is held until that read is ruled.
20+
- **The enumeration pin** loses eight lines and keeps the grid's `columns` as held for that ruling. One `z.unknown()` member is added and recorded: the rest of a static kanban card (its row's own values, beside the typed `id` and `title`).
2021

2122
## FROM → TO
2223

2324
| you wrote | write instead |
2425
|:--|:--|
25-
| `object-grid` `columns: [{ accessorKey: 'amount', header: 'Amount' }]` | `columns: [{ field: 'amount', label: 'Amount' }]` |
26-
| `object-grid` `columns: [{ name: 'amount' }]` | `columns: [{ field: 'amount' }]` |
27-
| `object-grid` `columns: ['name', { field: 'amount', width: 120 }]` | one spelling per list: `columns: [{ field: 'name' }, { field: 'amount', width: 120 }]` |
28-
| `object-grid` a column key the grid never reads, such as `editable` or `options` | delete the key (inline editing is the grid's `editable`) |
2926
| `object-grid` `fields: [{ field: 'name', width: 240 }]` | `fields: ['name']`, or the entry on `columns` |
3027
| `object-grid` `selection: 'multiple'` | `selection: { type: 'multiple' }` |
3128
| `object-grid` `selectable: 'none'` | `selectable: false`, or `selection: { type: 'none' }` |
@@ -41,6 +38,6 @@ The one-line fix: write each member as the list view declares it, or as the tabl
4138

4239
A writer is a page-component node: an object literal naming the type, a literal annotated with the block's type, a `schema={{…}}` on the block's React component, a call into a local helper that builds the node, or a direct parse through the row. Each member's value is read through same-file constants, and the control is `objectName` on the same nodes.
4340

44-
- **objectstack** at `49161683fb`, over `examples/`, `packages/` (with `packages/apps/`), `content/`, `skills/` and `apps/`: 57 `object-grid`, 30 `object-kanban` and 5 `object-calendar` nodes (the control on 47 / 27 / 4 of them). The authored values are 5 grid `columns` (field-name strings, the showcase's two grids among them) and 1 kanban `columns` (lanes, in the protocol docs); every one parses. No node authors another of the nine members.
45-
- **objectui** at the `.objectui-sha` pin `89cad75d55`: 689 `object-grid`, 240 `object-kanban` and 160 `object-calendar` nodes (the control on 293 / 108 / 98). 551 member values are static, and 503 of them parse. Each of the 48 that do not is a test fixture whose value the renderer drops, skips or refuses: 17 grid columns keyed `accessorKey` / `header` and 5 keyed `name` (the grid draws neither, and its own column diagnostic names both), 16 column keys the grid never reads (`editable` 14, `options` 2), a numeric `columns` and a column with no `field`, 2 object entries in `bulkActions` (the renderer skips them, and the tests assert the skip), 3 object entries in `fields` that copy the hand-off the list view makes to the grid at run time (not an authored page), a lane `color` (retired in the console; the test marks it an undeclared member), and 2 uses of the calendar's retired `dateField` / `endField` aliases (the test asserts their refusal). No refused value is one the renderer draws. 44 values are not static (helper parameters, `.map` results and the run-time hand-offs); none of them is an authored page.
41+
- **objectstack** at `49161683fb`, over `examples/`, `packages/` (with `packages/apps/`), `content/`, `skills/` and `apps/`: 57 `object-grid`, 30 `object-kanban` and 5 `object-calendar` nodes (the control on 47 / 27 / 4 of them). The one authored value among the eight members is a kanban `columns` (lanes, in the protocol docs), and it parses. No node authors another of the eight.
42+
- **objectui** at the `.objectui-sha` pin `89cad75d55`: 689 `object-grid`, 240 `object-kanban` and 160 `object-calendar` nodes (the control on 293 / 108 / 98). Across the eight members, 241 values are static, and 233 of them parse. Each of the 8 that do not is a test fixture whose value the renderer drops, skips or refuses: 2 object entries in `bulkActions` (the renderer skips them, and the tests assert the skip), 3 object entries in `fields` that copy the hand-off the list view makes to the grid at run time (not an authored page), a lane `color` (retired in the console; the test marks it an undeclared member), and 2 uses of the calendar's retired `dateField` / `endField` aliases (the test asserts their refusal). No refused value is one the renderer draws. 24 values are not static (helper parameters, `.map` results and the run-time hand-offs); none of them is an authored page. The grid's `columns` (310 static values) is held because 2 of them author a column `options` the grid draws in its group headers, a fixture written to pin that behaviour.
4643
- **Deployed metadata** was not measured.

‎packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts‎

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,45 @@
22

33
import type { SemanticMigration } from '../../types.js';
44

5-
// #21464 — the list members of the `object-grid`, `object-kanban` and
5+
// #21464 — eight list members of the `object-grid`, `object-kanban` and
66
// `object-calendar` page blocks were `z.unknown()` (an array of it for the
77
// lists) although each renderer reads them with a fixed shape, so an off-shape
88
// value passed the component-props gate and the block dropped or substituted it
99
// in silence. The rows now take the list view's own members by reference where
10-
// a list view declares one, and the measured shape otherwise. D3 only:
10+
// a list view declares one, and the measured shape otherwise. The grid's
11+
// `columns` is held at `z.unknown()`: the grid draws a column's `options`,
12+
// which the list view's column entry does not declare. D3 only:
1113
// page-component `properties` is not parsed on the metadata save or load path,
1214
// so a stored page is never refused; an off-shape value has no rewrite that
1315
// says what the author meant; and the authored census found no authored value
1416
// to respell — the refused values are fixtures probing that the renderer drops
1517
// them.
1618
export const entry: SemanticMigration = {
1719
id: 'ui-object-grid-kanban-calendar-list-members-typed',
18-
surface: 'page `object-grid` components — `properties.columns`, `.fields`, `.selection`, `.selectable`, '
20+
surface: 'page `object-grid` components — `properties.fields`, `.selection`, `.selectable`, '
1921
+ '`.rowActions`, `.bulkActions` and `.batchActions`; page `object-kanban` components — '
2022
+ '`properties.columns`; page `object-calendar` components — `properties.calendar` (which used to '
2123
+ 'accept any value)',
22-
replacement: 'the shape each block reads, the list view\'s own where it has one: `object-grid` `columns` '
23-
+ 'all field-name strings or all column entries `{ field, label?, width?, … }` (never mixed); `fields` '
24+
replacement: 'the shape each block reads, the list view\'s own where it has one: `object-grid` `fields` '
2425
+ 'field-name strings; `selection` `{ type }` with `none` / `single` / `multiple`; `selectable` `true`, '
2526
+ '`false`, `\'single\'` or `\'multiple\'`; `rowActions`, `bulkActions` and `batchActions` action-name '
2627
+ 'strings. `object-kanban` `columns` all lanes `{ id, title, cards?, limit?, className?, collapsed? }` '
2728
+ 'or all bare value strings (never mixed), a lane `id` a string. `object-calendar` `calendar` '
28-
+ '`{ startDateField, endDateField?, titleField?, colorField?, allDayField? }`. Rewrite a grid column '
29-
+ 'written `{ accessorKey, header }` or `{ name }` as `{ field, label }`; move an object entry of '
30-
+ '`fields` to `columns`; move a `{ name }` entry of `bulkActions` to `bulkActionDefs` or write the '
29+
+ '`{ startDateField, endDateField?, titleField?, colorField?, allDayField? }`. Move an object entry '
30+
+ 'of `fields` to `columns`; move a `{ name }` entry of `bulkActions` to `bulkActionDefs` or write the '
3131
+ 'bare name; style a lane with `className` instead of `color`; rename `dateField` / `endField` to '
3232
+ '`startDateField` / `endDateField`.',
3333
reason: 'Each renderer reads these members with one shape, and the page-component rows declared them '
3434
+ '`z.unknown()`, so any value passed the component-props gate and the block answered an off-shape one '
35-
+ 'with a silent default: a grid column keyed `accessorKey` or `name`, or an array mixing strings and '
36-
+ 'column objects, drew no column; an object entry of `fields` named no field; a `{ name }` entry of '
35+
+ 'with a silent default: an object entry of `fields` named no field; a `{ name }` entry of '
3736
+ '`bulkActions` was skipped; a kanban lane list mixing objects and strings drew a blank lane and swept '
3837
+ 'its records into the trailing lane; and a calendar block without `startDateField` placed no event. The rows '
39-
+ 'now take the list view\'s own `columns`, `selection`, `rowActions`, `bulkActions` (for `batchActions` '
38+
+ 'now take the list view\'s own `selection`, `rowActions`, `bulkActions` (for `batchActions` '
4039
+ 'too, the spelling the grid reads first) and `calendar` members by reference, and the measured shape '
4140
+ 'for the grid\'s `fields` and `selectable` and the kanban lane, so one value is judged the same way '
42-
+ 'on every door that carries it. It is read where every page component\'s props are: the '
41+
+ 'on every door that carries it. The grid\'s `columns` is not narrowed: its group-header labels read '
42+
+ 'an authored column\'s `options`, which the list view\'s column entry does not declare, so it stays '
43+
+ 'open until that read is ruled. It is read where every page component\'s props are: the '
4344
+ 'component-props gate reports a refused value as an advisory `component-props-invalid` / '
4445
+ '`component-props-unknown-key` finding on `objectstack validate`, `objectstack build` and '
4546
+ '`objectstack lint`, and a stored page still saves and loads, because a page component\'s '
@@ -49,7 +50,7 @@ export const entry: SemanticMigration = {
4950
+ 'the upgrader. Deployed metadata NOT MEASURED.',
5051
acceptanceCriteria: 'Every `object-grid`, `object-kanban` and `object-calendar` node validates: '
5152
+ '`objectstack validate` reports no `component-props-invalid` / `component-props-unknown-key` '
52-
+ 'finding under the nine members\' paths. Each block that set one of them now shows it: the declared '
53-
+ 'grid columns, the selection mode, the row and bulk actions, the kanban lanes with their records, and '
53+
+ 'finding under the eight members\' paths. Each block that set one of them now shows it: the grid\'s '
54+
+ 'field fallback, the selection mode, the row and bulk actions, the kanban lanes with their records, and '
5455
+ 'the calendar events placed by `startDateField`.',
5556
};

0 commit comments

Comments
 (0)