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
43 changes: 43 additions & 0 deletions .changeset/7804-object-kanban-handler-keys-judged.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
'@object-ui/types': minor
---

`ObjectKanbanSchema` now judges two of the three handler keys the kanban board reads off the
authored document (objectui#7804, the `plugin-kanban` slice; director seat ruling of
2026-09-07, decision batch #69): `onCardClick` and `onQuickAdd` are declared as objectui#6124
RUNTIME SLOTS — callable on the TypeScript face, refused BY NAME in the zod mirror because
JSON has no function value — and the message points at the node-type spelling an author can
write instead.

Until now neither was declared anywhere. `BaseSchema` is `.passthrough()`, so a key no arm
declares is not refused: it stops being judged and the value is KEPT, then reaches the
renderer that reads it. Measured on this branch, `{ "type": "object-kanban", "objectName":
"task", "groupBy": "status", "onCardClick": { "action": "toast" } }` parsed GREEN with
`{"action":"toast"}` surviving into the parsed output, and `KanbanRenderer` forwarded it to a
call site expecting a function. That is objectui#7664's measured transition, inherited by
this face when objectui#8802 retired the sibling `kanban` arm that used to carry all three as
runtime slots.

**BREAKING (scored `minor` per this repo's version-alignment convention, majors track
`@objectstack`)** — the accept set on the published zod mirror moves. A declared key is validated
even under `.passthrough()`, so two documents that parsed green yesterday are refused today,
each at its own path: `onCardClick` and `onQuickAdd` carried at all. Neither is authorable
in JSON by construction (a function has no JSON value), and `@objectstack/spec`'s
`ObjectKanbanPropsSchema` — fourteen keys on the installed 17.4.0 pin — already refuses both
by `unrecognized_keys`, so this narrows toward the protocol rather than away from it. Every
other key parses exactly as before, and an undeclared key still passes through unchanged.

**No renderer change; no runtime behaviour changes.** The registration, its `inputs` and
`ObjectKanban` are untouched. On the TypeScript face both keys become typed members where
`BaseSchema`'s index signature used to absorb them, so a wrong-typed value is now a compile
error at the key.

**The third key, `onCardMove`, is deliberately NOT declared, and that is a measurement rather
than an omission.** Its authored value reaches nothing on this face — `ObjectKanban`
substitutes its own mover on the schema it hands down and declares no `onCardMove` React
prop — which is the objectui#6124 `'retired'` disposition; `check:handler-key-reads` refuses
that spelling while `KanbanRenderer` still reads the key, printing `declares it RETIRED, but
a renderer still reads it`. The two spellings that would make the gate green are both worse:
`'runtime-slot'` would publish a callable key the object-bound board drops, and deleting the
read would narrow `KanbanRenderer`'s published props — a ruling, not a repair. The key keeps
its `KNOWN_UNDECLARED_READS` row naming objectui#7804, which stays open and stays the parent.
4 changes: 3 additions & 1 deletion content/docs/api/schema-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,9 @@ A drag-and-drop Kanban board. The `object-kanban` type key validates the shape t
>
> ⚠️ 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.
>
> The other keys the retired `kanban` arm alone declared — `cardTitle`, `swimlaneField`, `grouping` and `navigation` — are still undeclared on this face. The renderer reads them, so a board may carry them; they are simply not judged. The board's React host supplies `onCardMove` / `onCardClick` / `onQuickAdd` as props; none of the three is authorable in JSON.
> The other keys the retired `kanban` arm alone declared — `cardTitle`, `swimlaneField`, `grouping` and `navigation` — are still undeclared on this face. The renderer reads them, so a board may carry them; they are simply not judged.

> **Handler keys are not authorable in JSON, and two of the three now say so by name.** Since objectui#7804 this face declares `onCardClick` and `onQuickAdd` as objectui#6124 **runtime slots**: 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. ⚠️ `onCardMove` is the third key the board component reads and it is **still undeclared**: an authored one is accepted and dropped, because on an object-bound board the renderer substitutes its own mover. That gap is open on objectui#7804.

> `data` and `bind` are [`BaseSchema`](#baseschema) members, not narrowed here, but this face requires **one of** `bind`, `data`, `objectName` — the renderer's own record-source ladder (an external `data` prop → `bind` via `useDataScope` → this schema's own `data` → a fetch keyed by `objectName`). A purely static board (lanes carrying their own cards, no record source) authors `"groupBy"` and `"data": []`. ⚠️ The record-source rule is **separate** from the lane key and is unaffected by objectui#8990: omitting `groupBy` is fine, omitting all of `bind` / `data` / `objectName` is still refused, at the refinement rather than at `groupBy`.

Expand Down
11 changes: 11 additions & 0 deletions content/docs/plugins/plugin-kanban.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ const onCardMove = (cardId: string, fromCol: string, toCol: string, index: numbe
has no function value), never a member of the schema object below —
`packages/plugin-kanban/README.md` documents this the same way.

Neither are `onCardClick` and `onQuickAdd`, and since objectui#7804 the
`object-kanban` validator **says so by name**: both are declared as
objectui#6124 **runtime slots**, so an authored `onCardClick: { "action":
"toast" }` is now refused with a message pointing at the node-type spelling,
where before it parsed green and was handed to a call site expecting a function.
A React host still supplies either function through the TypeScript interface or
as a React prop; only the JSON face refuses them. ⚠️ `onCardMove` itself is
still *undeclared* on that validator — an authored one is accepted and silently
dropped, because an object-bound board substitutes its own mover — and that gap
is open on objectui#7804.

`columns` are the board's **swimlanes**, not a field projection — the fields drawn
on a card are `cardFields`. It is **one of two array shapes**, the pair
`@objectstack/spec` declares: an array of `{ id, title }` lanes (one per `groupBy`
Expand Down
Loading
Loading