You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit a7557a7
Browse filesBrowse the repository at this point in the historyBrowse files
feat(types): object-kanban declares grouping as the spec's GroupingConfig, by reference (objectui#11216) (#11568)
Fixes#11216
Clause-②: yes
## What this changes
`object-kanban` now declares `grouping` on both faces of
`@object-ui/types`, as `@objectstack/spec`'s `GroupingConfigSchema`, by
reference. The spec's `object-kanban` row has typed the key that way
since 17.6.0, which `main` installs. `ObjectKanban` reads
`grouping.fields[0].field` as the fallback for `swimlaneField`.
Before this change the arm did not declare the key, so the faces and the
spec gave three answers for one document:
| `grouping` value | spec row | strict face (before, now) | tolerant
face (before, now) |
|---|---|---|---|
| `{ fields: [{ field: 'owner' }] }` | accept | refused by name,
**accept** | kept, accept (kept as authored) |
| padded `{ fields: [{ field: ' x ' }] }` | refuse | refused by name,
refused at `grouping.fields.0.field` (`custom`) | kept, **refused** |
| bare string `'owner'` | refuse | refused by name, refused at
`grouping` (`invalid_type`) | kept, **refused** |
| `{ fields: [] }` | refuse | refused by name, refused at
`grouping.fields` (`too_small`) | kept, **refused** |
| undeclared key on the block or on an entry | refuse | refused by name
at the node, refused by name inside the block | kept, **refused** |
- **Zod mirror** (`packages/types/src/zod/objectql.zod.ts`): `grouping:
stripImportedDefaults(SpecGroupingConfigSchema).optional()`, which is
how `ObjectGridSchema.grouping` spells it. The strict face is derived
from the same declaration, so one member covers both runtime faces. The
spec's `order` and `collapsed` defaults are not added to a parsed
document. The comment that left the key to this card is gone.
- **TypeScript twin** (`packages/types/src/objectql.ts`): `grouping?:
GroupingConfig`. A type-level row pins it equal to
`SpecObjectKanbanProps['grouping']` and to `GroupingConfig | undefined`.
- **The renderer is unchanged.** `ObjectKanban.tsx` still reads
`swimlaneField || grouping.fields[0].field`.
## Descriptions that called the spec row `z.unknown()`
Each one now describes the typed row: it fixes the block's SHAPE, but
not which position this board reads.
-
`packages/plugin-kanban/src/__tests__/structuredKeysAreDeclaredAndHonoured-8313.test.ts`
(header).
-
`packages/plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx`
(header).
- `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`, the
`MEMBER_PINS` entry `object-kanban.grouping` (its `pins` text only; no
booked difference moved).
⚠️ **The card's location for the first description was wrong for `main`
at `ab18797`.** The card names "the kanban registration description
(`packages/plugin-kanban/src/index.tsx`)". That file has no `unknown()`
wording: a search for `unknown` finds only two unrelated code lines,
while `swimlaneField` (the control) is found. The `grouping` input
description there says only `fields[0].field` is read, which is still
true, so it is unchanged. The wording the card meant is in the three
places above. The console entry is the registration's member-pin
description, and the claim listed it as conditional surface.
## Also in this change
-
`packages/types/src/__tests__/grouping-field-padded-refused-7347.test.ts`:
its census of arms that declare `grouping` goes red when a new arm
declares it, so `object-kanban` gets a `DECLARING` row. The padded-name
refusal is now pinned on all four of the arm's faces.
- Docs: a `grouping` row in `content/docs/plugins/plugin-kanban.mdx` and
in `content/docs/api/schema-reference.md`. The schema reference
paragraph that said "`grouping` is still undeclared" is corrected.
- `.changeset/11216-kanban-grouping-typed.md`: `@object-ui/types` minor.
## Tests, measured at `cf8c860`
- **Red first, before the fix.** The new pin
`packages/types/src/__tests__/object-kanban-grouping-typed-11216.test.ts`
ran on the base tree: `Tests 28 failed | 12 passed (40)`. The strict
face refused the well-formed config with `unrecognized_keys` at the
node, and the tolerant faces kept every shape the spec refuses. (That
run used an earlier version of the pin's issue helper. The ablation
below is the measurement on the committed pin.)
- After the fix: `Test Files 1 passed (1)` and `Tests 40 passed (40)`.
- `pnpm exec vitest run packages/types/`: `Test Files 348 passed (348)`,
`Tests 9366 passed (9366)`.
- `pnpm exec vitest run packages/plugin-kanban/`: `Test Files 65 passed
(65)`, `Tests 442 passed (442)`.
- `pnpm exec vitest run
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`: `Tests
255 passed (255)`.
- Consumer radius: 11 test files in app-shell, plugin-view, plugin-list
and schema-catalog that feed `object-kanban` documents or grouping
through the validators, run without editing any of them: `Test Files 11
passed (11)`, `Tests 176 passed (176)`.
- `pnpm --filter @object-ui/types type-check` and `pnpm --filter
@object-ui/plugin-kanban type-check`: exit 0. Both script names were
echoed. `tsc -p tsconfig.test.json --listFiles` lists both edited
`packages/types` pins.
- `pnpm --workspace-concurrency=2 --filter
'@object-ui/plugin-kanban^...' build`: exit 0.
## Ablations, at `cf8c860`, through `ablation-replace.mjs` (anchor must
hit; restore proven by blob hash)
The subject resolves through relative imports to `src/`, so no `dist/`
leg applies.
1. **Zod member deleted.** The anchor hit 1 and went to 0. The blob went
from `4abab7e182f1` to `78fdd976f360`. The new pin plus the 7347 census
then gave `Tests 47 failed | 116 passed (163)`. Red rows: every
strict-face accept row, every refusal row on all three faces, every
verdict-parity row, the 7347 census, and its `object-kanban` rows.
Restored: the blob equals HEAD (`4abab7e182f1`) and `git diff HEAD` is
empty.
2. **TS member deleted.** `tsc -p tsconfig.test.json` exited 2 with
`TS2344` on both type rows of the new pin. It also gave two errors in
`zod-mirror-parity.test.ts`, whose declared-but-unmirrored ratchet names
`objectql.zod.ts#ObjectKanbanSchema` / `grouping`. Restored: the blob
equals HEAD (`9d326515ade6`) and `git diff HEAD` is empty. The first
attempt at this leg was refused by the tool and nothing landed: its
replacement text was a prefix of the anchor, so the count could not
rise. It was re-run with a marker replacement.
## Gates (local, at `cf8c860`)
`check:control-bytes` OK · `check:new-line-citations` "0 new
citation(s)" · `check:changeset-claims` exit 0 ·
`check:pending-changeset-literals` "No test source names a pending
changeset." · `check-changeset-presence` OK (1 changeset) ·
`check-changeset-no-major` OK · `check-changeset-overwrite` OK ·
`check:installed-pin-claims` OK · `check:spec-symbols` exit 0 ·
`check:doc-types` / `check:prompt-keys` / `check:doc-fences` /
`check:doc-example-ids` / `check-doc-links` OK ·
`check:component-surface-parity` / `check:designer-field-key-parity` /
`check:handler-key-reads` / `check:element-data-source-declaration` exit
0 · `check:test-path-roots` / `check:shell-escape-residue` /
`check:vi-mock-specifiers` OK. ESLint on the seven edited TS files
(`--no-inline-config --format json`): 7 files, 0 errors, 50
`no-explicit-any` warnings. All of those warnings are on lines this diff
does not touch, and the new pin has none. The repo-wide `pnpm lint`,
`pnpm check` (`objectui check`) and the full `pnpm test` are left to CI.
## Acceptance notes
- The pending `.changeset/11355-small-p1-sites.md` says "`grouping`, the
read's fallback, is still undeclared". It is a dated record of that
change, and `check-changeset-overwrite` refuses edits to existing
changesets, so it is left as is. Both entries will publish in the same
release, this one after it.
- `ObjectKanban.filterMembersReachTheWire-8176.test.tsx` (header) still
calls `filter`'s spec row `z.unknown().optional()`. On the installed
release, that row is an array of closed `{ field, operator, value }`
objects, and `sort` is not on the `object-kanban` row. The console
`MEMBER_PINS` entry `object-kanban.filter` repeats the same claim. Out
of this card's scope and not changed here. No one is set to pick it up.
- Out of surface and untouched: open PR objectui#11512's files (none of
its 88 overlap this diff, read from its file list), and the files of the
sibling cards objectui#10872 and objectui#10380.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
`ObjectKanbanSchema.grouping` is declared on both faces, as `@objectstack/spec`'s `GroupingConfig`, by reference (objectui#11216).
6
+
7
+
The spec's `object-kanban` row types `grouping` as the list view's own `GroupingConfigSchema`. `ObjectKanban` reads `grouping.fields[0].field` as the fallback for `swimlaneField`. Until this change the key was undeclared on the `object-kanban` arm, so this package and the spec gave three different answers for one document:
8
+
9
+
|`grouping` value | spec row | strict authoring face (before → now) | tolerant face (before → now) |
10
+
|---|---|---|---|
11
+
|`{ fields: [{ field: 'owner' }] }`| accepted | refused by name → **accepted**| kept → accepted, kept as authored |
12
+
| a padded field name, `{ fields: [{ field: ' x ' }] }`| refused | refused by name → refused at `fields.0.field`| kept → **refused**|
13
+
| a bare string, `'owner'`| refused | refused by name → refused at `grouping`| kept → **refused**|
14
+
| an empty list, `{ fields: [] }`| refused | refused by name → refused at `fields`| kept → **refused**|
15
+
| an undeclared key on the block or on an entry | refused | refused by name → refused by name inside the block | kept → **refused**|
16
+
17
+
-**Zod mirror.**`grouping: stripImportedDefaults(GroupingConfigSchema).optional()`, which is how `ObjectGridSchema.grouping` spells it. The spec's `order` and `collapsed` defaults are not added to a parsed document.
18
+
-**TypeScript.**`grouping?: GroupingConfig`, the spec's authored type. It is the same type as the `object-kanban` row's `grouping`.
19
+
20
+
**Not changed: the board.**`ObjectKanban` reads `fields[0].field` and nothing else in the block. The registration's input description says so, and it is unchanged.
21
+
22
+
**Migration.** Write `grouping` as `{ fields: [{ field }] }` with an unpadded field name, or delete it and author `swimlaneField`, which the board reads first.
23
+
24
+
**minor, not patch.** The member is new in the shipped `.d.ts` and in the zod mirror's `.shape`. The tolerant face now refuses the shapes in the table that it used to keep unjudged.
Copy file name to clipboardExpand all lines: .changeset/11355-small-p1-sites.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,3 +17,12 @@ Small reader sites stop riding `BaseSchema`'s index signature (objectui#11355, p
17
17
-`@object-ui/plugin-map`: the dev warning about flat map keys that a `map` block shadows reads those keys through their own type, not through a `Record` conversion.
18
18
19
19
**minor, not patch, for `@object-ui/types`.** Both members are new in the shipped `.d.ts` and in the zod mirror's `.shape`, and the mirror now refuses a wrongly typed value for each, where it used to keep one unjudged.
20
+
21
+
⚠️ **Dated note, 2026-10-03 — `ObjectKanbanSchema.grouping` is now declared — objectui#11216.**
22
+
At this change `grouping`, the fallback `ObjectKanban` reads for `swimlaneField`,
23
+
was undeclared on both faces, as the first bullet above says. objectui#11216
24
+
declares it on both faces in this release, by reference: the TypeScript member is
25
+
the spec's `GroupingConfig`, and the zod mirror takes the spec's
26
+
`GroupingConfigSchema`, the type the `object-kanban` row gives the key. So the
27
+
sentence above that calls `grouping` "still undeclared" does not hold in this
28
+
release. The rest of this entry is kept as the reading of this change.
pins: 'ONE nested position and no more: `schema.grouping?.fields?.[0]?.field` is the FALLBACK source of `swimlaneField`, and that is the entire member contract this board carries for the key. Three rows make it a reading rather than a claim — the swimlane layout appears keyed by `fields[0].field` where without the key there is none; an explicit `swimlaneField` WINS over it; and a second `fields` entry changes nothing, which is what pins the read at `[0]` rather than at "the fields list". The declared description says the rest is inert precisely so the declaration does not recommend a write the renderer cannot honour — this file is what keeps that sentence true. The spec row is `z.unknown()`, so the read site is the whole member contract (objectui#8313).',
3021
+
pins: 'ONE nested position and no more: `schema.grouping?.fields?.[0]?.field` is the FALLBACK source of `swimlaneField`, and that is the entire member contract this board carries for the key. Three rows make it a reading rather than a claim — the swimlane layout appears keyed by `fields[0].field` where without the key there is none; an explicit `swimlaneField` WINS over it; and a second `fields` entry changes nothing, which is what pins the read at `[0]` rather than at "the fields list". The declared description says the rest is inert precisely so the declaration does not recommend a write the renderer cannot honour — this file is what keeps that sentence true. The spec row types the block as the list view\'s `GroupingConfigSchema`, which both `@object-ui/types` faces take by reference (objectui#11216): that fixes its SHAPE, not which position this board reads, so the read site is still the whole of what the board does with a member (objectui#8313).',
Copy file name to clipboardExpand all lines: content/docs/api/schema-reference.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1010,6 +1010,7 @@ A drag-and-drop Kanban board. The `object-kanban` type key validates the shape t
1010
1010
|`titleField`|`string`| Field used as the card title. |
1011
1011
|`cardFields`|`string[]`| Fields rendered on each card. |
1012
1012
|`swimlaneField`|`string`| Record field that splits the board into horizontal swimlanes, across the `groupBy` columns. Declared on both faces since objectui#11355, with the type `@objectstack/spec`'s `object-kanban` row gives it. With the key absent the board falls back to `grouping.fields[0].field`. |
1013
+
|`grouping`|`GroupingConfig`| The fallback for `swimlaneField`: with that key absent, the board splits into swimlanes by `grouping.fields[0].field`, and reads nothing else in the block. The type is `@objectstack/spec`'s `GroupingConfig` (`{ fields: [{ field, order?, collapsed? }] }`), the one the `object-kanban` row and `object-grid` take, by reference on both faces since objectui#11216: at least one entry, no undeclared key, and a `field` written without leading or trailing spaces. |
1013
1014
|`filter`|`any[]`| Query filter, forwarded verbatim as `$filter`. |
1014
1015
|`limit`|`number`| Fetch window for the board (default 100). |
1015
1016
|`coverImageField`|`string`| Field whose URL renders as the card cover image. |
@@ -1024,7 +1025,7 @@ A drag-and-drop Kanban board. The `object-kanban` type key validates the shape t
1024
1025
>
1025
1026
> ⚠️ The **bare-string array applies only to a board with no `groupBy`.** It is declared so this package does not refuse an authoring the protocol allows. The renderer reads a bare-string lane list only when a board has no `groupBy` — so on a board that *does* declare one the strings are ignored and the lanes come from the group field's picklist options or from the data. Since objectui#8990 made `groupBy` optional, a lane-less board is a valid authoring and this arm is live on it: the lanes are drawn, titled by the **raw strings** (a grouped board titles its lanes with the picklist *labels* instead). ⚠️ Such a board holds **no cards** — with no lane key the records are never distributed — and dragging a card writes nothing back. It is lane headings, not a populated board; to control the lanes of a working board, declare `groupBy` and write the `{ id, title }` array.
1026
1027
>
1027
-
> Of the other keys the retired `kanban` arm alone declared, `cardTitle` (objectui#9606), `navigation` (objectui#8652) and `swimlaneField` (objectui#11355) are declared on this face;`grouping`is still undeclared. The renderer reads it as the fallback for `swimlaneField`, so a board may carry it; it is simply not judged. Aligning it with the spec's typed row is objectui#11216.
1028
+
> Of the other keys the retired `kanban` arm alone declared, `cardTitle` (objectui#9606), `navigation` (objectui#8652), `swimlaneField` (objectui#11355) and `grouping` (objectui#11216) are declared on this face.`grouping`was the last of them: until objectui#11216 it was kept unjudged here and refused by name on the strict authoring face.
1028
1029
1029
1030
> **Handler keys are not authorable in JSON, and all three now say so by name.** Since objectui#7804 this face declares `onCardClick` as an objectui#6124 **runtime slot**: a React host supplies the function through the TypeScript interface or as a React prop, and this validator **refuses the key by name** with a message pointing at the node-type spelling (`{ "type": "toast", … }`, an `action:button` node). Until then an authored `onCardClick: { "action": "toast" }` parsed **green** — `BaseSchema` is `.passthrough()`, so a key no arm declares is not refused, it stops being judged and the value is kept, then reaches a call site expecting a function. ⭐ The other two keys are **tombstones**, not runtime slots, so their TypeScript twins are `?: never` rather than callable. `onCardMove` has been one since objectui#9342: an authored one reached **nothing** even as a function, because an object-bound board substitutes its own mover, and the mover lives on `KanbanRenderer`'s React prop of the same name, a sibling of its `schema`. `onQuickAdd` has been one since objectui#11234: a supplied one reached the board and was never called, because its partner `quickAdd` is retired here. `ObjectKanban` renders an internal board that takes the Quick Add pair only as explicit props and supplies neither half; the pair lives on `KanbanRenderer`'s `schema`, for a React host.
Copy file name to clipboardExpand all lines: content/docs/plugins/plugin-kanban.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -174,6 +174,7 @@ interface KanbanCard {
174
174
|`limit`| number | Rows fetched by an object-driven board (default 100) |
175
175
|`navigation`|`ViewNavigationConfig`| What a card click opens — the spec's `NavigationConfig` by reference, the type `ObjectGridSchema.navigation` uses: `mode` (`page`, `drawer`, `modal`, `split`, `popover`, `new_window` or `none`) with `size`, `openNewTab` and `preventNavigation`. With the key absent a click opens the record in a drawer, and a click handler from a parent view outranks the whole key. `page` — and a block written without `mode`, which takes the spec's `page` default — opens the record page through the record navigator the host publishes (objectui#11293); the console publishes one on its custom pages, record pages and list views, and under a host that publishes none the click opens nothing. |
176
176
|`swimlaneField`| string | Record field that splits an object-driven board into horizontal swimlanes, across the `groupBy` columns. `@objectstack/spec` declares it on `object-kanban`, and both faces of `@object-ui/types` declare it since objectui#11355. With the key absent the board falls back to `grouping.fields[0].field`|
177
+
|`grouping`|`GroupingConfig`| The fallback for `swimlaneField`: with that key absent, the board splits into swimlanes by `grouping.fields[0].field`. Nothing else in the block is read on this board. The type is `@objectstack/spec`'s `GroupingConfig`, `{ fields: [{ field, order?, collapsed? }] }`, which both faces of `@object-ui/types` take by reference since objectui#11216: at least one entry, no undeclared key, and a `field` without leading or trailing spaces |
0 commit comments