Carved out of the contract review of PR #7700 (comment 5550273792, item 8), which raised it as a package-wide question rather than that PR's defect and therefore did not block on it. #7700 merged 2026-09-05T07:40:44Z as 8ad218d58; without this card the finding would survive only inside a review comment on a merged PR.
Two arity conventions for one kind of slot, measured on origin/main 8ad218d58
| declaration |
arity |
packages/types/src/base.ts:828 EventHandlers.onClick?: (event?: any) => void | Promise<void> |
takes the event |
packages/types/src/form.ts:62 ButtonSchema.onClick?: () => void | Promise<void> |
takes nothing |
packages/types/src/overlay.ts:160 AlertDialogSchema.onAction?: () => void (new, from #7700) |
takes nothing |
Census over packages/types/src/*.ts (tests excluded): 11 handler slots declared on…?: () => …, across crud.ts 2, form.ts 2, overlay.ts 2, and one each in data-display.ts, layout.ts, objectql.ts, ui-action.ts, views.ts. 8 declared on…?: (event?: any) => … — 7 in base.ts, 1 in complex.ts. Both spellings are live and neither is rare.
The concrete consequence
An arity-0 slot refuses a handler that takes the event. For AlertDialogSchema.onAction that rules out Radix's own documented idiom on AlertDialog.Action — calling event.preventDefault() to keep the dialog open — because there is no parameter to call it on. The (event?: any) spelling accepts both shapes and is equally forwardable, so the arity-0 form is strictly the narrower of the two.
⛔ This is not a defect in #7700. That PR followed the convention its own neighbour uses (ButtonSchema.onClick), which is the right call for one card; the question is which convention the package should have, and that is nobody's single card until it is written down.
Explicitly NOT a finding here
The missing | Promise<void> on onAction compared with ButtonSchema.onClick looks like a second gap and is not one: TypeScript's return-type-void rule makes () => Promise<void> assignable to () => void, so an async handler is accepted either way. Measured and recorded so it is not re-raised.
What is owed
A ruling on which arity the package publishes for runtime slots, then a sweep. ⛔ Do not open this by changing one slot — a per-slot fix is what produced the split. Note also that these are objectui#6124 runtime slots: the Zod mirrors refuse them by name (handlerKeyRefusal), so the arity lives only on the TS face and no accept set moves either way.
⚠️ Clause-② on the implementation is yes if the sweep widens arity-0 slots to take an event — it changes what the published TS face accepts. Re-measure the census at dispatch time; packages/types is under heavy change and this reading is a snapshot.
Generated by Claude Code
Carved out of the contract review of PR #7700 (comment 5550273792, item 8), which raised it as a package-wide question rather than that PR's defect and therefore did not block on it. #7700 merged 2026-09-05T07:40:44Z as
8ad218d58; without this card the finding would survive only inside a review comment on a merged PR.Two arity conventions for one kind of slot, measured on
origin/main8ad218d58packages/types/src/base.ts:828EventHandlers.onClick?: (event?: any) => void | Promise<void>packages/types/src/form.ts:62ButtonSchema.onClick?: () => void | Promise<void>packages/types/src/overlay.ts:160AlertDialogSchema.onAction?: () => void(new, from #7700)Census over
packages/types/src/*.ts(tests excluded): 11 handler slots declaredon…?: () => …, acrosscrud.ts2,form.ts2,overlay.ts2, and one each indata-display.ts,layout.ts,objectql.ts,ui-action.ts,views.ts. 8 declaredon…?: (event?: any) => …— 7 inbase.ts, 1 incomplex.ts. Both spellings are live and neither is rare.The concrete consequence
An arity-0 slot refuses a handler that takes the event. For
AlertDialogSchema.onActionthat rules out Radix's own documented idiom onAlertDialog.Action— callingevent.preventDefault()to keep the dialog open — because there is no parameter to call it on. The(event?: any)spelling accepts both shapes and is equally forwardable, so the arity-0 form is strictly the narrower of the two.⛔ This is not a defect in #7700. That PR followed the convention its own neighbour uses (
ButtonSchema.onClick), which is the right call for one card; the question is which convention the package should have, and that is nobody's single card until it is written down.Explicitly NOT a finding here
The missing
| Promise<void>ononActioncompared withButtonSchema.onClicklooks like a second gap and is not one: TypeScript's return-type-voidrule makes() => Promise<void>assignable to() => void, so anasynchandler is accepted either way. Measured and recorded so it is not re-raised.What is owed
A ruling on which arity the package publishes for runtime slots, then a sweep. ⛔ Do not open this by changing one slot — a per-slot fix is what produced the split. Note also that these are objectui#6124 runtime slots: the Zod mirrors refuse them by name (
handlerKeyRefusal), so the arity lives only on the TS face and no accept set moves either way.Clause-②on the implementation is yes if the sweep widens arity-0 slots to take an event — it changes what the published TS face accepts. Re-measure the census at dispatch time;packages/typesis under heavy change and this reading is a snapshot.Generated by Claude Code