Repository navigation
fix(slack): read Block Kit message bodies from layout blocks - #1062
Merged
Merged
Conversation
Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
Contributor
|
@privatenumber is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
…r renders Read the body from blocks only when every block is a section, header, context, image, divider, actions, or table block. Messages with any other block type keep `text` as the body, so content the parser can't render still reaches the message. Tables now stay in block order within the body, each text object renders as its own paragraph, and `message.links` reads URLs from the block mrkdwn when it forms the body. `resolvedContent` resolves inline mentions in the raw text itself. Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
…k-body Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com> # Conflicts: # packages/adapter-slack/sample-messages.md
bensabic
approved these changes
Oct 19, 2026
bensabic
enabled auto-merge (squash)
October 19, 2026 01:32
3 of 5 tasks
bensabic
added a commit
that referenced
this pull request
Oct 27, 2026
## Summary Fixes #956. Bulleted and numbered lists typed in Slack's message composer now parse to mdast `list` nodes, including nested items. The special-mentions half of #956 was fixed in #960. This PR covers the list half, which still reproduces on `main`: Slack flattens composer lists in `event.text` to lines starting with `•` or `◦`, which are not markdown list syntax, so the issue's payload parses to a single paragraph. With this change it gives: ``` nodes : ["paragraph","list","paragraph"] markdown: "Steps:\n\n* one\n* two\n * nested\n\n@here and @devs\n" ``` ## Approach The list structure exists only in `event.blocks`, as `rich_text_list` elements with `style`, `indent`, and `offset`. When a message's blocks contain a list and every non-table block is `rich_text`, the new `richTextBody` (`src/rich-text.ts`) renders the blocks to the same mrkdwn dialect `event.text` uses, with markdown list markers. `content()` and `resolvedContent()` use that body instead of `event.text` on the text path, so mention resolution, the format converter, and table handling are unchanged. Every other message, including the layout-block bodies from #1062, keeps its current path. - Nested lists arrive as sibling `rich_text_list` elements with a higher `indent`. Each item is indented to its parent's content column, so `- ` and `10. ` parents both nest correctly, and ordered lists continue from `offset` across a nested list. - Inline styles share markers across consecutive elements: bold `make `, bold italic `this`, bold ` work` renders as `*make _this_ work*`. A style change that mrkdwn cannot express (bold starting mid-word, or a closing marker pressed against an opening one) keeps the text and drops only that formatting, instead of leaving stray `*` in the output. - Styles on links, mentions, and emoji are kept, and `@here` inside inline code stays literal, matching how `event.text` parses. - Block elements carry only IDs, so labels for `<!subteam^…|@handle>` and `<#C…|name>` are copied from `event.text`. - Trailing newlines are trimmed with a loop. The `/\n+$/` regex flagged by CodeQL took about 39 seconds on 200,000 newlines in the new test. - Link labels in rich text are now entity-escaped, so a `>` in a label (including in table cells, which share `blocktext`) no longer ends the link early. `SlackMessageBlock["style"]` gains `bold`, `italic`, and `strike`. It stays an object type, so existing reads of `block.style?.code` still compile; a type test guards this. `message.text` follows the core `toPlainText` output for lists, so it now reads `one\ntwo` instead of `• one\n• two`. ## Test plan - `src/rich-text.test.ts`: renderer logic, checked as mrkdwn and as markdown after the adapter's format converter (nesting columns, offsets, multi-line items, style nesting and mid-word changes, punctuation, line breaks, code, links, mention labels, quotes and code blocks, gating, and linear-time trimming). The trimming and link-label tests fail against the previous implementation. - `src/rich-text.test-d.ts`: the `style` type stays an object, and `richTextBody`'s signature. - `src/index.test.ts`: the issue's payload through `parseMessage` and the async parser, user mention resolution and bot-mention detection inside list items, a table next to a list, the `event.text` fallback, and a `>` in a table cell link label. The list tests fail on `main`. - `vitest run` in `packages/adapter-slack` (963 tests, no type errors), `tsc --noEmit`, `ultracite check`, `pnpm knip`, and `pnpm konsistent` pass. The full `packages/integration-tests` suite passes (63 files, 1,425 tests). - Smoke test against the built packages: signed `event_callback` requests through `chat.webhooks.slack` with a memory state reach `onDirectMessage` and `onNewMention` with the expected list markdown, and a message without a list keeps `event.text`. The branch merged `main` to resolve conflicts with #1062. The first implementation is backed out in two commits before the merge and reapplied after it. ## Checklist - [x] All commits are signed and verified - [ ] All commits are signed off for the DCO (`git commit -s`) - [ ] `pnpm validate` passes - [x] Changeset added (or N/A — see [CONTRIBUTING.md](./CONTRIBUTING.md)) - [x] Documentation updated (or N/A) --------- Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com> Co-authored-by: roboeve[bot] <311457182+roboeve[bot]@users.noreply.github.com> Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sectionblock, the handler gets the request it was invoked forProblem
A Slack message can carry both
textandblocks. When it carries blocks, Slack renders the blocks and usestextonly as "a fallback string to display in notifications" (chat.postMessage). Apps and workflows rely on that: the fallback is often a one-line summary, and the content people read is in the blocks.The adapter builds
message.textandmessage.formattedfromtext. From the message's own blocks it reads only tables. Text insection,header, andcontextblocks never reaches the bot, so the bot works from the notification summary while people in the channel see the full message.This affects a bot in two ways.
An app mentions the bot. An internal tool asks the bot to act:
{ "type": "app_mention", "text": "New request", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "<@U_BOT> please check the deploy" } } ] }app_mentionfor the token in thesectionblock, even thoughtexthas none.isMention: true.text, somessage.textisNew request.onNewMentionruns without the request it was invoked for, and the bot can only ask what to do.The bot reads an app's message in history. A deploy app posts
text: "Deployment failed"with the error, project, and links insectionblocks. A user replies in the thread and asks the bot what went wrong.fetchMessages,fetchChannelMessages, andfetchMessageparse messages the same way, so the bot's view of the alert isDeployment failed, without the details the user is looking at.Changes
When a message has no
rich_textblocks, text from its layout blocks becomes the body, in block order:sectiontext andfields,headertext, andcontexttext elementsmrkdwnobjects keep their markup and mentions, which resolve to display names like the rest of the bodyplain_textobjects render literally, so*and_stay charactersThe same message now gives
message.text=@Test Bot please check the deploy.These rules keep existing behavior:
Typed messages keep
text. A Slack client sendsrich_textblocks thattextmirrors, so a message with anyrich_textblock keeps thetextbody. #1002 covers composer lists in that path.Blocks without text keep
text. When the layout blocks carry no text, as in a message with only buttons or images,textstays the body.Table placement is unchanged. Tables from the message's blocks keep their position around the body.
Mention detection is unchanged. Only the message content changes.
packages/adapter-slack/src/index.ts:layoutBlockContent, rendered through the existing attachment-content pathpackages/adapter-slack/src/index.test.ts: a layout message parsed by both parse paths, a webhook mention in asectionblock, and a buttons-only message that keeps its fallbackpackages/adapter-slack/sample-messages.md: a capturedsectionmention payload, anonymized