Skip to content

Conversation

@davidcelis
Copy link

@davidcelis davidcelis commented Apr 2, 2025

This pull request is my attempt to spike out a solution for #176, providing a way for handling BlockKit's various limits around text length, number of blocks/elements, etc. I know that one other pull request was opened with a potential solution for this (#180) but that PR has stagnated and I wanted to provide an alternative (and hopefully simpler) approach anyway.

Similarly to 180, my approach involves prepending blocks with relevant Limiter classes, starting with a limiter that truncates provided block_ids to Slack's required length (255 characters). This behavior is off by default, though I think it would be a good idea to turn it on by default in a major release so that there's ample warning for existing users. I initially had this controlled by passing an option (e.g. autofix: true) to blocks that should support this, with the idea that you would likely do something like Slack::BlockKit.blocks(autofix: true) { ... } and have it propagate down through the builder, but I worried that supporting this keyword argument and passing it down long chains of blocks would be cumbersome, so I thought a global configuration option would probably be a simpler start. I'm not sure how likely it is that users would want to selectively enable or disable autofixing anyway, but we could always introduce that in the future and use the config as the default.

Either way, the result of this would be configuring the gem during some initialization process:

Slack::BlockKit.config do |c|
  c.autofix_invalid_blocks = true
end

# Alternatively, just:
Slack::BlockKit.config.autofix_invalid_blocks = true

Once enabled, any block with a block_id (currently just layout blocks) would automatically truncate that block_id if necessary. This truncation occurs at the time of rendering, so it doesn't modify the object in-place; only the resulting JSON (this felt the least surprising to me).

Also, I'm open to any feedback or naming changes you have, of course! For instance, even though I started with block_id, it and action_id strike me as limits that users might want to configure differently, since those identifiers matter when handling interactivity. Users might not want us to truncate those because they'd have to be careful to keep our truncated IDs as their reference and not the ID they passed in originally

@davidcelis davidcelis force-pushed the autofix branch 2 times, most recently from dc5019a to cb88bc8 Compare April 3, 2025 00:16
stop ||= length_with_room_for_omission

"#{text[0, stop]}#{omission}"
end
Copy link
Author

@davidcelis davidcelis Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I used Rails' implementation of truncate for a couple reasons

  1. Certain things should maybe truncate differently (e.g. block_id and action_id don't need to truncate with an ellipsis at the end, though that's supposedly still valid), so the omission and separator options give us control
  2. I could see us adding configuration options on top of autofix_invalid_blocks that allow users to specify how they'd like text to truncate (e.g. Slack::BlockKit.config.truncate_invalid_text_with = "…" or something)

@davidcelis
Copy link
Author

I started working on a limiter for blocks that have a text field with a length limit as a second example, and even with that, it starts to spiral a bit with how much variety there is. I have a test limiter that takes an argument for a configurable length, e.g:

class Slack::BlockKit::Layout::Section
  prepend Limiters::Text.new(length: 3000)
end

class Slack::BlockKit::Layout::Header
  prepend Limiters::Text.new(length: 150)
end

Which works, but for the section block, it only really solves one aspect of their requirement, which is that the text can't be super long. It doesn't handle the fact that it has to be present (unless fields are provided) or, that if no text or fields are provided, it's unclear what the autofix should be (if there should be one at all). If the goal is to munge blocks into something that's postable at all costs, it could be as basic as forcing the text to be a single space (which is apparently valid). Either way, there are some complex requirements involving multiple fields (e.g. sections must have either text or fields, a modal must have submit if it contains any input in any nested block, etc.) that would probably have to be implemented as separate, non-reusable limiters/autofixers.

I've started to feel like ActiveModel validations would make these requirements much easier to codify, and that something like ActiveSupport's after_validate callbacks would be where a block could be autofixed if configured to do so. But re-implementing all blocks as ActiveModel objects would be a big change, and some maintainers are hesitant to bring dependencies as large as ActiveSupport/ActiveModel into their project.

Definitely curious if you've given BlockKit limit/validation handling any more thought over the last year 😅 And I really share your wish that Slack would just republish their JSON Schema.

@CGA1123
Copy link
Owner

CGA1123 commented Apr 7, 2025

Spent some time today starting to go through what the actual limits/validations are that Slack has documented so that I could get a better overall view of what any kind of client-side validation and related data-munging might entail.

Most of the validations are still relatively simple, I wonder whether it is worth implementing some json-schema definition for this and seeing how that might hook into this library nicely.

If we can avoid activesupport/activemodel that would be quite nice, I don't want to really have to think about testing across versions if at all possible!

I guess the key requirement there is as you mentioned having some kind of after_validate callback/hook to allow for munging the data as required, which seems relatively feasible to implement, the API will just need to make sure that enough metadata is present in order to do any required autofixing.


Limits/validations defined by Slack related to BlockKit

Surfaces

Message

  • blocks: Maximum 50 elements.

Modal

  • blocks: Maximum 100 elements.

Home

  • blocks: Maximum 100 elements.

Layouts

Actions

  • elements: Maximum 25 elements.
  • block_id: Maximum 255 characters.

Context

  • elements: Maximum 10 elements.
  • elements[*].type: Must of types: image, mrkdwn, or plain_text
  • block_id: Maximum 255 characters.

Divider

  • block_id: Maximum 255 characters.

Header

  • text.type: Must be of type plain_text
  • text.text: Maximum 150 characters.
  • block_id: Maximum 255 characters.

Image

  • alt_text: Maximum 2000 characters.
  • image_url: Maximum 3000 characters.
  • title.type: Must be plain_text.
  • title.text: Maximum 2000 characters.
  • block_id: Maximum 255 characters.
  • One of slack_file or image_url must be present.

Input

  • label.type: Must be plain_text.
  • label.text: Maximum 2000 characters.
  • hint.type: Must be plain_text.
  • hint.text: Maximum 2000 characters.
  • block_id: Maximum 255 characters.
  • Cannot set dispatch_action true and provide element.type as file_input.

Markdown

  • text: Maximum 12000 characters.
  • block_id: Maximum 255 characters.

RichText

  • elements[*].type: Must be one of rich_text_section, rich_text_list, rich_text_preformatted, rich_text_quote
  • block_id: Maximum 255 characters.

Section

  • text.text: Minimum 1 character, Maximum 3000 characters.
  • block_id: Maximum 255 characters.
  • fields: Maximum 10 elements.
  • fields[*].text: Maximum 2000 characters.
  • One of fields or text must be set.

Video

  • author_name: Maximum 49 characters.
  • block_id: Maximum 255 characters.
  • description.type: Must be plain_text.
  • description.text: Maximum 199 characters.
  • title.type: Must be plain_text.
  • title.text: Maximum 199 characters.
  • video_url: Must be HTTPS.
  • video_url: Must match unfurl domains for sending slack app.

Composition

Confirmation Dialog

  • title.type: Must be plain_text
  • title.text: Maximum 100 characters.
  • text.type: Must be plain_text
  • text.text: Maximum 300 characters.
  • confirm.type: Must be plain_text
  • confirm.text: Maximum 30 characters.
  • deny.type: Must be plain_text
  • deny.text: Maximum 30 characters.
  • style: One of danger, primary

Conversation Filter

  • include[*]: Must be one of im, mpim, private, public

Dispatch Action Configuration

  • trigger_actions_on[*]: Must be one of on_enter_pressed, on_character_entered

Option

  • text.text: Maximum 75 characters.
  • text.type:
    • Overflow, Select, Multi-Select: Must be plain_text.
  • value: Must be unique within the scope of the parent
    • XXX: What does this mean? Docs say "A unique string value that will be passed to your app when this option is chosen."
  • value: Maximum 75 characters.
  • url: Only valid in Overflow Menus.
  • url: Maximum 3000 characters.

Option Group

  • label.text: Maximum 75 characters.
  • label.type: Must be plain_text.
  • options: Maximum 100 elements.

Text

  • type: One of plain_text or mrkdwn.
  • text: Minimum 1 character, Maximum 3000 characters.
  • emoji: Only usable with plain_text.
  • verbatim: Only usable with mrkdwn.

Elements

Button

  • text.text: Maximum 30 characters.
  • action_id: Maximum 255 characters.
  • url: Maximum 3000 characters.
  • value: Maximum 2000 characters.
  • style: One of danger, primary
  • accessibility_label: Maximum 75 characters.

Checkboxes

  • action_id: Maximum 255 characters.
  • options: Maximum 10 elements.
  • initial_options: Maximum 10 elements.
  • initial_options: Must be a subset or equal to options.

Date picker

  • action_id: Maximum 255 characters.
  • initial_date: Should be in YYYY-MM-DD format.
  • placeholder.text: Maximum 150 characters.
  • placeholder.type: Must be plain_text.

Datetime picker

  • action_id: Maximum 255 characters.
  • initial_date_time: 10 digit unix time.

Email picker

  • action_id: Maximum 255 characters.
  • placeholder.text: Maximum 150 characters.
  • placeholder.type: Must be plain_text.

File input

  • action_id: Maximum 255 characters.
  • max_files: Must be between 1 and 10 (inclusive).

Image

  • image_url: Maximum 3000 characters.
  • One of image_url or slack_file must be specified.

Multi channels select

Multi conversations select

Multi external select

Multi static select

Multi user select

Number input

Overflow menu

Plain-text input

Radio button group

Rich text input

Channel select

Conversation select

User select

External select

Static select

Time picker

URL input

Workflow button

@davidcelis
Copy link
Author

If we can avoid activesupport/activemodel that would be quite nice, I don't want to really have to think about testing across versions if at all possible!

Yeah, that's totally reasonable! Once I got that idea in my head, I decided I really wanted to try pursuing that so ultimately I ended up building out my own library to do this 😅 I'll be focusing on that instead of this spike, but I'll leave this here for you to close out (or build off of if you're still interested in solving it and would like to use it)!

@CGA1123
Copy link
Owner

CGA1123 commented Apr 17, 2025

Nice! https://github.com/davidcelis/block-kit looks great :shipit:

@davidcelis
Copy link
Author

(Actually, I'll close it so it doesn't appear in my active list anymore 😅 you're still free to use it however you like, of course!)

@davidcelis davidcelis closed this Jun 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants