The reaction handler now receives the conversation `thread` and an
update-capable `messageRef` for the reacted message — the same surface an
`onClick` gets via `ctx.thread`/`ctx.message.ref`. A reaction can now post new
UI (`thread.post`), swap the message in place (`thread.update(messageRef, …)`),
run the agent, or block on a human choice (`thread.awaitChoice`, HITL).
- bot-ui: `MessageReaction` gains `thread` and `messageRef`.
- platform-adapter: `IncomingReaction` gains an optional adapter-provided
`messageRef` (engine falls back to `{ id: messageId }`).
- create-bot: threads `thread` + `messageRef` into both the global
`ReactionEvent` and the per-message handler.
- Slack/Discord/Telegram reaction decoders emit a platform-specific,
update-capable `messageRef` (channel+ts / channelId+id / chatId+messageId).
Type the bot UI surface and broaden the cross-platform component vocabulary,
adding only capabilities that more than one adapter can express.
- bot-ui: type the `Thread` interface's `ui` params (`post`/`update`/
`awaitChoice`/`postEphemeral`) as `Renderable` instead of `unknown`, so JSX
is checked at the call site and the `{ raw }` escape hatch is explicit.
- `<Message onReaction>`: per-message reaction callback `(emoji, { added, user,
rawEmoji, messageId })`. Stripped from the IR before it reaches the adapter,
routed from reaction ingress by message id. Durable on the same terms as a
component `onClick` — a `{ component, props }` snapshot is persisted and the
component is re-rendered to re-derive the handler after a restart; inline
handlers route in-process only.
- `Button.url` link buttons — Slack, Discord, Teams, Telegram.
- `Field.label` — typed (Discord and Telegram already consumed it untyped);
newly rendered on Slack.
- `Select.multi` multi-select — Slack (`multi_static_select` in an input block
+ `selected_options` decode), Discord (min/max values + `component` bounds
decode), Teams (`isMultiSelect`); Telegram/WhatsApp degrade to single-select.
Slack-only capabilities (Button.confirm, Image.title, Input label/initialValue/
required, Select.initialValue) were intentionally left out.
Pure JSX runtime (no React, no Slack) producing a BotNode IR tree.
Statically typed component props via a package-owned JSX namespace:
unknown attributes, bad values, and bad children are compile errors.
Components: Message, Header, Section, Markdown, Field, Context,
Actions, Button, Select, Input, Image, Divider; bind() escape hatch
for non-serializable handler captures.