Found while measuring objectui#7804's plugin-kanban slice (PR objectui#9338). ⛔ Not fixed there — it is a behaviour defect in ObjectKanban's click wiring, and that PR changes no renderer.
The defect
An onCardClick supplied to an object-kanban board runs twice for one card click.
ObjectKanban hands the same function to useNavigationOverlay as its onRowClick, and then calls it again itself:
const externalClick = onRowClick ?? onCardClick;
const navigation = useNavigationOverlay({ navigation: navConfig, objectName: schema.objectName, onRowClick: externalClick });
...
onCardClick: (card, event) => {
navigation.handleClick(card, event);
onCardClick?.(card);
},
useNavigationOverlay's handleClick gives onRowClick full priority — it calls it and returns — so when a host supplies onCardClick and no onRowClick, that one function is the value of externalClick AND the one the wrapper calls on the next line. One click, two calls.
Measured
Driven through the real SchemaRenderer on the production path (packages/plugin-kanban/src/__tests__/handlerKeyDispositionsMeasured-7804.test.tsx, the leg whose subject is the sibling key): a document { type: 'object-kanban', columns: [...], onCardClick: spy } rendered, then the handler the board implementation was handed invoked once. The spy's call count came back 2, where the leg had predicted 1. That reading is what made the control fail; the control was then relaxed to "it RAN", deliberately, so that fixing this card does not redden a file that is not about it.
Firing control on the same run: the sibling onCardMove spy came back 0 on the same document and the same render, so the 2 is a reading about this key and not a recorder that counts everything twice.
Why it matters
The two call sites are not interchangeable. handleClick forwards the modifier event (onRowClick(record, event)) so a host can implement Cmd/Ctrl/middle-click; the wrapper's second call passes the record only. So a host handler that navigates fires once with modifiers and once without — a duplicate navigation, a duplicate analytics event, or a double-open, depending on what it does.
⚠️ Not reached when the board is embedded in an ObjectView: that parent supplies onRowClick, so externalClick resolves to the parent's function and the author's onCardClick is called exactly once by the wrapper. The defect is specific to a host that supplies onCardClick alone.
Not the same as its neighbours
- objectui#8285 —
quickAdd is inert on object-kanban because ObjectKanban supplies no onQuickAdd. Different key, opposite shape (nothing fires, rather than one thing firing twice).
- objectui#7804 — whether the arm DECLARES these keys. This one is about how many times a declared, reaching one runs.
What is not decided here
Which of the two calls should go. Dropping the wrapper's onCardClick?.(card) keeps the modifier event and makes the board behave like the grid; dropping externalClick's onCardClick arm keeps the wrapper's plain-record signature but changes which handler wins when a parent also supplies onRowClick. They are not equivalent for a host, so this is a ruling rather than a mechanical edit.
Reported by the os-dev seat implementing the objectui#7804 plugin-kanban slice, with Claude Code — session session_01UzHd6hDYatoDn17BuwKxnZ.
Generated by Claude Code
Found while measuring objectui#7804's
plugin-kanbanslice (PR objectui#9338). ⛔ Not fixed there — it is a behaviour defect inObjectKanban's click wiring, and that PR changes no renderer.The defect
An
onCardClicksupplied to anobject-kanbanboard runs twice for one card click.ObjectKanbanhands the same function touseNavigationOverlayas itsonRowClick, and then calls it again itself:useNavigationOverlay'shandleClickgivesonRowClickfull priority — it calls it and returns — so when a host suppliesonCardClickand noonRowClick, that one function is the value ofexternalClickAND the one the wrapper calls on the next line. One click, two calls.Measured
Driven through the real
SchemaRendereron the production path (packages/plugin-kanban/src/__tests__/handlerKeyDispositionsMeasured-7804.test.tsx, the leg whose subject is the sibling key): a document{ type: 'object-kanban', columns: [...], onCardClick: spy }rendered, then the handler the board implementation was handed invoked once. The spy's call count came back 2, where the leg had predicted 1. That reading is what made the control fail; the control was then relaxed to "it RAN", deliberately, so that fixing this card does not redden a file that is not about it.Firing control on the same run: the sibling
onCardMovespy came back 0 on the same document and the same render, so the 2 is a reading about this key and not a recorder that counts everything twice.Why it matters
The two call sites are not interchangeable.
handleClickforwards the modifier event (onRowClick(record, event)) so a host can implement Cmd/Ctrl/middle-click; the wrapper's second call passes the record only. So a host handler that navigates fires once with modifiers and once without — a duplicate navigation, a duplicate analytics event, or a double-open, depending on what it does.ObjectView: that parent suppliesonRowClick, soexternalClickresolves to the parent's function and the author'sonCardClickis called exactly once by the wrapper. The defect is specific to a host that suppliesonCardClickalone.Not the same as its neighbours
quickAddis inert onobject-kanbanbecauseObjectKanbansupplies noonQuickAdd. Different key, opposite shape (nothing fires, rather than one thing firing twice).What is not decided here
Which of the two calls should go. Dropping the wrapper's
onCardClick?.(card)keeps the modifier event and makes the board behave like the grid; droppingexternalClick'sonCardClickarm keeps the wrapper's plain-record signature but changes which handler wins when a parent also suppliesonRowClick. They are not equivalent for a host, so this is a ruling rather than a mechanical edit.Reported by the
os-devseat implementing the objectui#7804plugin-kanbanslice, with Claude Code — sessionsession_01UzHd6hDYatoDn17BuwKxnZ.Generated by Claude Code