From cf43e312026b204880ad57f797458ef28fd76315 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 26 Jun 2026 16:58:00 -0700 Subject: [PATCH 1/7] feat(block-kit): add carousel block example Add a Python Block Kit example for the carousel block, mirroring the official docs example: a carousel of three cards, each with an icon, title, subtitle, hero image, body, and an action button. Includes a test asserting the produced block matches the expected payload, and a README bullet linking the reference and implementation. Ref: https://docs.slack.dev/reference/block-kit/blocks/carousel-block Co-Authored-By: Claude --- block-kit/README.md | 1 + block-kit/src/blocks/carousel.py | 82 ++++++++++++++++++ block-kit/tests/blocks/test_carousel.py | 110 ++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 block-kit/src/blocks/carousel.py create mode 100644 block-kit/tests/blocks/test_carousel.py diff --git a/block-kit/README.md b/block-kit/README.md index ee77ff0..70cd5d6 100644 --- a/block-kit/README.md +++ b/block-kit/README.md @@ -9,6 +9,7 @@ Read the [docs](https://docs.slack.dev/block-kit/) to learn concepts behind thes ### Blocks - **[Actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block)**: Holds multiple interactive elements. [Implementation](./src/blocks/actions.py). +- **[Carousel](https://docs.slack.dev/reference/block-kit/blocks/carousel-block)**: Displays a scrollable, horizontal collection of cards. [Implementation](./src/blocks/carousel.py). - **[Context](https://docs.slack.dev/reference/block-kit/blocks/context-block)**: Provides contextual info, which can include both images and text. [Implementation](./src/blocks/context.py). - **[Context actions](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block)**: Displays actions as contextual info, which can include both feedback buttons and icon buttons. [Implementation](./src/blocks/context_actions.py). - **[Divider](https://docs.slack.dev/reference/block-kit/blocks/divider-block)**: Visually separates pieces of info inside of a message. [Implementation](./src/blocks/divider.py). diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py new file mode 100644 index 0000000..60299c8 --- /dev/null +++ b/block-kit/src/blocks/carousel.py @@ -0,0 +1,82 @@ +from slack_sdk.models.blocks import CardBlock, CarouselBlock +from slack_sdk.models.blocks.basic_components import MarkdownTextObject, PlainTextObject +from slack_sdk.models.blocks.block_elements import ButtonElement, ImageElement + + +def example01() -> CarouselBlock: + """ + Displays a scrollable, horizontal collection of cards. + https://docs.slack.dev/reference/block-kit/blocks/carousel-block/ + + A carousel with three cards, each with an icon, title, subtitle, hero + image, body, and an action button. + """ + block = CarouselBlock( + elements=[ + CardBlock( + block_id="carousel-card-1", + icon=ImageElement( + image_url="https://picsum.photos/36/36", + alt_text="Icon", + ), + title=MarkdownTextObject(text="MDR"), + subtitle=MarkdownTextObject(text="Refining data files"), + hero_image=ImageElement( + image_url="https://picsum.photos/400/300", + alt_text="Sample hero image", + ), + body=MarkdownTextObject(text="Blue badge required to gain access."), + actions=[ + ButtonElement( + text=PlainTextObject(text="Action Button", emoji=False), + action_id="button_action_1", + ) + ], + ), + CardBlock( + block_id="carousel-card-2", + icon=ImageElement( + image_url="https://picsum.photos/36/36", + alt_text="Icon", + ), + title=MarkdownTextObject(text="O&D"), + subtitle=MarkdownTextObject( + text="Storage, maintenance, and rotation of art pieces" + ), + hero_image=ImageElement( + image_url="https://picsum.photos/400/300", + alt_text="Sample hero image", + ), + body=MarkdownTextObject(text="Green badge required to gain access."), + actions=[ + ButtonElement( + text=PlainTextObject(text="Action Button", emoji=False), + action_id="button_action_2", + ) + ], + ), + CardBlock( + block_id="carousel-card-3", + icon=ImageElement( + image_url="https://picsum.photos/36/36", + alt_text="Icon", + ), + title=MarkdownTextObject(text="Wellness Center"), + subtitle=MarkdownTextObject(text="Wellness sessions"), + hero_image=ImageElement( + image_url="https://picsum.photos/400/300", + alt_text="Sample hero image", + ), + body=MarkdownTextObject( + text="Please take a seat in the waiting room until called." + ), + actions=[ + ButtonElement( + text=PlainTextObject(text="Action Button", emoji=False), + action_id="button_action_3", + ) + ], + ), + ], + ) + return block diff --git a/block-kit/tests/blocks/test_carousel.py b/block-kit/tests/blocks/test_carousel.py new file mode 100644 index 0000000..5f15e28 --- /dev/null +++ b/block-kit/tests/blocks/test_carousel.py @@ -0,0 +1,110 @@ +import json + +from src.blocks import carousel + + +def test_example01(): + block = carousel.example01() + actual = block.to_dict() + expected = { + "type": "carousel", + "elements": [ + { + "type": "card", + "block_id": "carousel-card-1", + "icon": { + "type": "image", + "image_url": "https://picsum.photos/36/36", + "alt_text": "Icon", + }, + "title": {"type": "mrkdwn", "text": "MDR"}, + "subtitle": {"type": "mrkdwn", "text": "Refining data files"}, + "hero_image": { + "type": "image", + "image_url": "https://picsum.photos/400/300", + "alt_text": "Sample hero image", + }, + "body": { + "type": "mrkdwn", + "text": "Blue badge required to gain access.", + }, + "actions": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Action Button", + "emoji": False, + }, + "action_id": "button_action_1", + } + ], + }, + { + "type": "card", + "block_id": "carousel-card-2", + "icon": { + "type": "image", + "image_url": "https://picsum.photos/36/36", + "alt_text": "Icon", + }, + "title": {"type": "mrkdwn", "text": "O&D"}, + "subtitle": { + "type": "mrkdwn", + "text": "Storage, maintenance, and rotation of art pieces", + }, + "hero_image": { + "type": "image", + "image_url": "https://picsum.photos/400/300", + "alt_text": "Sample hero image", + }, + "body": { + "type": "mrkdwn", + "text": "Green badge required to gain access.", + }, + "actions": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Action Button", + "emoji": False, + }, + "action_id": "button_action_2", + } + ], + }, + { + "type": "card", + "block_id": "carousel-card-3", + "icon": { + "type": "image", + "image_url": "https://picsum.photos/36/36", + "alt_text": "Icon", + }, + "title": {"type": "mrkdwn", "text": "Wellness Center"}, + "subtitle": {"type": "mrkdwn", "text": "Wellness sessions"}, + "hero_image": { + "type": "image", + "image_url": "https://picsum.photos/400/300", + "alt_text": "Sample hero image", + }, + "body": { + "type": "mrkdwn", + "text": "Please take a seat in the waiting room until called.", + }, + "actions": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Action Button", + "emoji": False, + }, + "action_id": "button_action_3", + } + ], + }, + ], + } + assert json.dumps(actual, sort_keys=True) == json.dumps(expected, sort_keys=True) From e361a307c06fddad25d1f5b7a9a52e0bfc0a8857 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 4 Aug 2026 15:06:51 -0700 Subject: [PATCH 2/7] fix(block-kit): suppress arg-type on carousel card icon/hero_image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CardBlock.icon and hero_image take Block Kit image element objects per the card block reference (type/image_url/alt_text), but slack_sdk types them as str | None, so ImageElement args trip mypy [arg-type]. Add # type: ignore[arg-type] on each, matching the card example. (slack_sdk typing bug — tracked separately.) Co-Authored-By: Claude --- block-kit/src/blocks/carousel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py index 60299c8..476ce6a 100644 --- a/block-kit/src/blocks/carousel.py +++ b/block-kit/src/blocks/carousel.py @@ -15,13 +15,13 @@ def example01() -> CarouselBlock: elements=[ CardBlock( block_id="carousel-card-1", - icon=ImageElement( + icon=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/36/36", alt_text="Icon", ), title=MarkdownTextObject(text="MDR"), subtitle=MarkdownTextObject(text="Refining data files"), - hero_image=ImageElement( + hero_image=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), @@ -35,7 +35,7 @@ def example01() -> CarouselBlock: ), CardBlock( block_id="carousel-card-2", - icon=ImageElement( + icon=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/36/36", alt_text="Icon", ), @@ -43,7 +43,7 @@ def example01() -> CarouselBlock: subtitle=MarkdownTextObject( text="Storage, maintenance, and rotation of art pieces" ), - hero_image=ImageElement( + hero_image=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), @@ -57,13 +57,13 @@ def example01() -> CarouselBlock: ), CardBlock( block_id="carousel-card-3", - icon=ImageElement( + icon=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/36/36", alt_text="Icon", ), title=MarkdownTextObject(text="Wellness Center"), subtitle=MarkdownTextObject(text="Wellness sessions"), - hero_image=ImageElement( + hero_image=ImageElement( # type: ignore[arg-type] image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), From be7dabdacb165d30c4cfbd6be19bcccf4ef2b1bc Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 4 Aug 2026 16:06:42 -0700 Subject: [PATCH 3/7] revert(block-kit): drop # type: ignore on carousel card icon/hero_image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Examples should not carry type-checker suppressions. Remove the # type: ignore[arg-type] added for the ImageElement icon/hero_image args. Until slackapi/python-slack-sdk#1937 lands (widening CardBlock.icon / hero_image to accept an image element), mypy reports [arg-type] here by design — the honest signal that slack_sdk mis-types these fields as str. Co-Authored-By: Claude --- block-kit/src/blocks/carousel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py index 476ce6a..60299c8 100644 --- a/block-kit/src/blocks/carousel.py +++ b/block-kit/src/blocks/carousel.py @@ -15,13 +15,13 @@ def example01() -> CarouselBlock: elements=[ CardBlock( block_id="carousel-card-1", - icon=ImageElement( # type: ignore[arg-type] + icon=ImageElement( image_url="https://picsum.photos/36/36", alt_text="Icon", ), title=MarkdownTextObject(text="MDR"), subtitle=MarkdownTextObject(text="Refining data files"), - hero_image=ImageElement( # type: ignore[arg-type] + hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), @@ -35,7 +35,7 @@ def example01() -> CarouselBlock: ), CardBlock( block_id="carousel-card-2", - icon=ImageElement( # type: ignore[arg-type] + icon=ImageElement( image_url="https://picsum.photos/36/36", alt_text="Icon", ), @@ -43,7 +43,7 @@ def example01() -> CarouselBlock: subtitle=MarkdownTextObject( text="Storage, maintenance, and rotation of art pieces" ), - hero_image=ImageElement( # type: ignore[arg-type] + hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), @@ -57,13 +57,13 @@ def example01() -> CarouselBlock: ), CardBlock( block_id="carousel-card-3", - icon=ImageElement( # type: ignore[arg-type] + icon=ImageElement( image_url="https://picsum.photos/36/36", alt_text="Icon", ), title=MarkdownTextObject(text="Wellness Center"), subtitle=MarkdownTextObject(text="Wellness sessions"), - hero_image=ImageElement( # type: ignore[arg-type] + hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), From 3800844bcfb16bb3158e65d8e814be659f8f3e8d Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 4 Aug 2026 21:45:23 -0700 Subject: [PATCH 4/7] fix(carousel): match description to docs.slack.dev wording Co-Authored-By: Claude --- block-kit/src/blocks/carousel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py index 60299c8..0862a2f 100644 --- a/block-kit/src/blocks/carousel.py +++ b/block-kit/src/blocks/carousel.py @@ -5,7 +5,7 @@ def example01() -> CarouselBlock: """ - Displays a scrollable, horizontal collection of cards. + Displays related card blocks in a horizontally-scrolling container. https://docs.slack.dev/reference/block-kit/blocks/carousel-block/ A carousel with three cards, each with an icon, title, subtitle, hero From 7553de7f58046262eadf13db7ae1428ad5524ba8 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 9 Sep 2026 10:06:03 -0700 Subject: [PATCH 5/7] docs(block-kit): align carousel description to docs and simplify example doc - README: use the docs carousel-block description ("Displays related card blocks in a horizontally-scrolling container.") to match the docstring first line. - carousel.py: simplify the specific-example line to "A sample carousel block." (docs description first line + URL kept per AGENTS.md). Co-Authored-By: Claude --- block-kit/README.md | 2 +- block-kit/src/blocks/carousel.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/block-kit/README.md b/block-kit/README.md index 59d779e..828da87 100644 --- a/block-kit/README.md +++ b/block-kit/README.md @@ -11,7 +11,7 @@ Read the [docs](https://docs.slack.dev/block-kit/) to learn concepts behind thes - **[Actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block)**: Holds multiple interactive elements. [Implementation](./src/blocks/actions.py). - **[Alert](https://docs.slack.dev/reference/block-kit/blocks/alert-block)**: Displays alerts, warnings, and informational messages. [Implementation](./src/blocks/alert.py). - **[Card](https://docs.slack.dev/reference/block-kit/blocks/card-block)**: Displays content in a card. [Implementation](./src/blocks/card.py). -- **[Carousel](https://docs.slack.dev/reference/block-kit/blocks/carousel-block)**: Displays a scrollable, horizontal collection of cards. [Implementation](./src/blocks/carousel.py). +- **[Carousel](https://docs.slack.dev/reference/block-kit/blocks/carousel-block)**: Displays related card blocks in a horizontally-scrolling container. [Implementation](./src/blocks/carousel.py). - **[Context](https://docs.slack.dev/reference/block-kit/blocks/context-block)**: Provides contextual info, which can include both images and text. [Implementation](./src/blocks/context.py). - **[Context actions](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block)**: Displays actions as contextual info, which can include both feedback buttons and icon buttons. [Implementation](./src/blocks/context_actions.py). - **[Divider](https://docs.slack.dev/reference/block-kit/blocks/divider-block)**: Visually separates pieces of info inside of a message. [Implementation](./src/blocks/divider.py). diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py index 0862a2f..2cf9ef2 100644 --- a/block-kit/src/blocks/carousel.py +++ b/block-kit/src/blocks/carousel.py @@ -8,8 +8,7 @@ def example01() -> CarouselBlock: Displays related card blocks in a horizontally-scrolling container. https://docs.slack.dev/reference/block-kit/blocks/carousel-block/ - A carousel with three cards, each with an icon, title, subtitle, hero - image, body, and an action button. + A sample carousel block. """ block = CarouselBlock( elements=[ From f2ae148f580075405aa3cb5255da69e9079bf3ae Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 9 Sep 2026 10:08:16 -0700 Subject: [PATCH 6/7] test(block-kit): expand carousel title/subtitle to one field per line Expand the inline title/subtitle text objects in the carousel test's expected dict so each JSON field is on its own line, consistent with the rest of the expected payload. Co-Authored-By: Claude --- block-kit/tests/blocks/test_carousel.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/block-kit/tests/blocks/test_carousel.py b/block-kit/tests/blocks/test_carousel.py index 5f15e28..ff3d96d 100644 --- a/block-kit/tests/blocks/test_carousel.py +++ b/block-kit/tests/blocks/test_carousel.py @@ -17,8 +17,14 @@ def test_example01(): "image_url": "https://picsum.photos/36/36", "alt_text": "Icon", }, - "title": {"type": "mrkdwn", "text": "MDR"}, - "subtitle": {"type": "mrkdwn", "text": "Refining data files"}, + "title": { + "type": "mrkdwn", + "text": "MDR", + }, + "subtitle": { + "type": "mrkdwn", + "text": "Refining data files", + }, "hero_image": { "type": "image", "image_url": "https://picsum.photos/400/300", @@ -48,7 +54,10 @@ def test_example01(): "image_url": "https://picsum.photos/36/36", "alt_text": "Icon", }, - "title": {"type": "mrkdwn", "text": "O&D"}, + "title": { + "type": "mrkdwn", + "text": "O&D", + }, "subtitle": { "type": "mrkdwn", "text": "Storage, maintenance, and rotation of art pieces", @@ -82,8 +91,14 @@ def test_example01(): "image_url": "https://picsum.photos/36/36", "alt_text": "Icon", }, - "title": {"type": "mrkdwn", "text": "Wellness Center"}, - "subtitle": {"type": "mrkdwn", "text": "Wellness sessions"}, + "title": { + "type": "mrkdwn", + "text": "Wellness Center", + }, + "subtitle": { + "type": "mrkdwn", + "text": "Wellness sessions", + }, "hero_image": { "type": "image", "image_url": "https://picsum.photos/400/300", From be02db1a2b988cdf1b8f29cfc4377c8f107d7c5c Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 9 Sep 2026 10:18:38 -0700 Subject: [PATCH 7/7] fix(block-kit): set verbatim=False on carousel mrkdwn to match docs The docs carousel example renders each card's title/subtitle/body mrkdwn object with "verbatim": false. Set verbatim=False on every MarkdownTextObject in the example and assert it in the test so the payload matches docs.slack.dev. Each field is on its own line. Co-Authored-By: Claude --- block-kit/src/blocks/carousel.py | 41 +++++++++++++++++++------ block-kit/tests/blocks/test_carousel.py | 9 ++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/block-kit/src/blocks/carousel.py b/block-kit/src/blocks/carousel.py index 2cf9ef2..70bb1a7 100644 --- a/block-kit/src/blocks/carousel.py +++ b/block-kit/src/blocks/carousel.py @@ -18,13 +18,22 @@ def example01() -> CarouselBlock: image_url="https://picsum.photos/36/36", alt_text="Icon", ), - title=MarkdownTextObject(text="MDR"), - subtitle=MarkdownTextObject(text="Refining data files"), + title=MarkdownTextObject( + text="MDR", + verbatim=False, + ), + subtitle=MarkdownTextObject( + text="Refining data files", + verbatim=False, + ), hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), - body=MarkdownTextObject(text="Blue badge required to gain access."), + body=MarkdownTextObject( + text="Blue badge required to gain access.", + verbatim=False, + ), actions=[ ButtonElement( text=PlainTextObject(text="Action Button", emoji=False), @@ -38,15 +47,22 @@ def example01() -> CarouselBlock: image_url="https://picsum.photos/36/36", alt_text="Icon", ), - title=MarkdownTextObject(text="O&D"), + title=MarkdownTextObject( + text="O&D", + verbatim=False, + ), subtitle=MarkdownTextObject( - text="Storage, maintenance, and rotation of art pieces" + text="Storage, maintenance, and rotation of art pieces", + verbatim=False, ), hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), - body=MarkdownTextObject(text="Green badge required to gain access."), + body=MarkdownTextObject( + text="Green badge required to gain access.", + verbatim=False, + ), actions=[ ButtonElement( text=PlainTextObject(text="Action Button", emoji=False), @@ -60,14 +76,21 @@ def example01() -> CarouselBlock: image_url="https://picsum.photos/36/36", alt_text="Icon", ), - title=MarkdownTextObject(text="Wellness Center"), - subtitle=MarkdownTextObject(text="Wellness sessions"), + title=MarkdownTextObject( + text="Wellness Center", + verbatim=False, + ), + subtitle=MarkdownTextObject( + text="Wellness sessions", + verbatim=False, + ), hero_image=ImageElement( image_url="https://picsum.photos/400/300", alt_text="Sample hero image", ), body=MarkdownTextObject( - text="Please take a seat in the waiting room until called." + text="Please take a seat in the waiting room until called.", + verbatim=False, ), actions=[ ButtonElement( diff --git a/block-kit/tests/blocks/test_carousel.py b/block-kit/tests/blocks/test_carousel.py index ff3d96d..9abd39e 100644 --- a/block-kit/tests/blocks/test_carousel.py +++ b/block-kit/tests/blocks/test_carousel.py @@ -20,10 +20,12 @@ def test_example01(): "title": { "type": "mrkdwn", "text": "MDR", + "verbatim": False, }, "subtitle": { "type": "mrkdwn", "text": "Refining data files", + "verbatim": False, }, "hero_image": { "type": "image", @@ -33,6 +35,7 @@ def test_example01(): "body": { "type": "mrkdwn", "text": "Blue badge required to gain access.", + "verbatim": False, }, "actions": [ { @@ -57,10 +60,12 @@ def test_example01(): "title": { "type": "mrkdwn", "text": "O&D", + "verbatim": False, }, "subtitle": { "type": "mrkdwn", "text": "Storage, maintenance, and rotation of art pieces", + "verbatim": False, }, "hero_image": { "type": "image", @@ -70,6 +75,7 @@ def test_example01(): "body": { "type": "mrkdwn", "text": "Green badge required to gain access.", + "verbatim": False, }, "actions": [ { @@ -94,10 +100,12 @@ def test_example01(): "title": { "type": "mrkdwn", "text": "Wellness Center", + "verbatim": False, }, "subtitle": { "type": "mrkdwn", "text": "Wellness sessions", + "verbatim": False, }, "hero_image": { "type": "image", @@ -107,6 +115,7 @@ def test_example01(): "body": { "type": "mrkdwn", "text": "Please take a seat in the waiting room until called.", + "verbatim": False, }, "actions": [ {