Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e67f0da
feat(06-01): add icon fields to SlackAppConfig and getBotForShortcut …
tyom Feb 21, 2026
04178e6
feat(06-01): extend getConnectedBots API and make dispatchShortcut ta…
tyom Feb 21, 2026
dd5ccc5
feat(06-02): extend types and add shortcut aggregation for multi-bot …
tyom Feb 21, 2026
f5231af
feat(06-02): restructure context menu with Connect to apps submenu
tyom Feb 21, 2026
c72b7c4
feat: add showcase message shortcut and fix shortcut payload type
tyom Feb 21, 2026
00f64c7
fix: prevent Electron drag region from blocking image preview close b…
tyom Feb 21, 2026
919d297
feat: add Connect to apps submenu to three-dot message menu
tyom Feb 21, 2026
b893988
fix: unify file image rendering with ImageBlock and fix shortcut imag…
tyom Feb 21, 2026
4c23344
fix: hide image collapse toggle in modals to match real Slack
tyom Feb 21, 2026
6c56a0e
fix: replace required field asterisk with validation error on submit …
tyom Feb 21, 2026
cb80e07
fix: add red border on validation error and live revalidation after f…
tyom Feb 21, 2026
84db29c
fix: match real Slack modal width (520px)
tyom Feb 21, 2026
d393936
Format the code
tyom Feb 22, 2026
b5c128f
feat: display bot icon from registration config
tyom Feb 22, 2026
5750963
feat: group sequential messages by same author
tyom Feb 22, 2026
983619c
Format code
tyom Feb 22, 2026
99f0ccd
fix: address review findings across shortcuts and UI
tyom Feb 22, 2026
4a0e6bd
fix: support bare domain links in mrkdwn and use compact line breaks …
tyom Feb 22, 2026
542f30a
fix: use alias for app module imports in components
tyom Feb 22, 2026
f06a252
fix: render bot icon as image in shortcut menus when it's a URL
tyom Feb 22, 2026
277fe99
Format code
tyom Feb 22, 2026
c8f3994
fix: address review findings across JSON templates, imports, and mrkd…
tyom Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/showcase-bot/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ slack:
- command: /showcase
description: Block Kit showcase commands
usage_hint: '[generate|clear|modal|help]'
shortcuts: []
shortcuts:
- callback_id: showcase_message_context
name: Showcase message context
description: Show message details in a modal
type: message
actions: {}
modals:
showcase_modal: showcase_modal
Expand Down
2 changes: 2 additions & 0 deletions apps/showcase-bot/src/listeners/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { App } from '@slack/bolt'
import * as commands from './commands/showcase'
import * as actions from './actions/showcase-actions'
import * as shortcuts from './shortcuts/showcase-shortcuts'

export function registerListeners(app: App) {
commands.register(app)
actions.register(app)
shortcuts.register(app)
}
89 changes: 89 additions & 0 deletions apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { App, MessageShortcut } from '@slack/bolt'
import type { KnownBlock } from '@slack/types'
import { slackLogger } from '../../utils/logger'

export function register(app: App) {
app.shortcut<MessageShortcut>(
{ callback_id: 'showcase_message_context', type: 'message_action' },
async ({ shortcut, ack, client }) => {
await ack()

const { message, channel, user } = shortcut

// Truncate text to stay within Slack's 3000-char mrkdwn limit
const maxTextLen = 3000 - '```\n\n```'.length - '*Text*\n'.length
const rawText = message.text || ''
const truncatedText =
rawText.length > maxTextLen
? rawText.slice(0, maxTextLen - 3) + '...'
: rawText

const fields = [
['Timestamp', message.ts],
['Channel', channel.id],
['User', user.id],
['Text', truncatedText ? `\`\`\`${truncatedText}\`\`\`` : '_empty_'],
]

const blocks: KnownBlock[] = fields.map(([label, value]) => ({
type: 'section' as const,
text: {
type: 'mrkdwn' as const,
text: `*${label}*\n${value}`,
},
}))

// Show attached files
const files = message.files as
| Array<{ url_private?: string; mimetype?: string }>
| undefined
if (files?.length) {
for (const file of files) {
if (file.url_private && file.mimetype?.startsWith('image/')) {
blocks.push({
type: 'image',
image_url: file.url_private,
alt_text: 'Attached image',
})
}
}
}

// Show images from Block Kit blocks
const msgBlocks = message.blocks as
| Array<{
type: string
image_url?: string
alt_text?: string
title?: { text?: string }
}>
| undefined
if (msgBlocks?.length) {
for (const block of msgBlocks) {
if (block.type === 'image' && block.image_url) {
blocks.push({
type: 'image',
image_url: block.image_url,
alt_text: block.alt_text || block.title?.text || 'Image',
})
}
}
}

try {
await client.views.open({
trigger_id: shortcut.trigger_id,
view: {
type: 'modal',
title: { type: 'plain_text', text: 'Message Context' },
close: { type: 'plain_text', text: 'Close' },
blocks,
},
})
slackLogger.info('Opened message context modal')
} catch (err) {
slackLogger.error({ err }, 'Failed to open message context modal')
}
}
)
}
109 changes: 109 additions & 0 deletions apps/showcase-bot/src/messages/blocks/13-template-approval.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "You have a new request:\n*<google.com|Fred Enriquez - Time Off request>*"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Type:*\nPaid time off\n*When:*\nAug 10-Aug 13\n*Hours:* 16.0 (2 days)\n*Remaining balance:* 32.0 hours (4 days)\n*Comments:* \"Family in town, going camping!\""
},
"accessory": {
"type": "image",
"image_url": "https://api.slack.com/img/blocks/bkb_template_images/approvalsNewDevice.png",
"alt_text": "computer thumbnail"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Approve"
},
"style": "primary",
"action_id": "showcase_approval_approve_pto",
"value": "click_me_123"
},
{
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Deny"
},
"style": "danger",
"action_id": "showcase_approval_deny_pto",
"value": "click_me_123"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "You have a new request:\n*<fakeLink.toEmployeeProfile.com|Fred Enriquez - New device request>*"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Type:*\nComputer (laptop)"
},
{
"type": "mrkdwn",
"text": "*When:*\nSubmitted Aug 10"
},
{
"type": "mrkdwn",
"text": "*Last Update:*\nMar 10, 2015 (3 years, 5 months)"
},
{
"type": "mrkdwn",
"text": "*Reason:*\nAll vowel keys aren't working."
},
{
"type": "mrkdwn",
"text": "*Specs:*\n\"Cheetah Pro 15\" - Fast, really fast"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Approve"
},
"style": "primary",
"action_id": "showcase_approval_approve_device",
"value": "click_me_123"
},
{
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Deny"
},
"style": "danger",
"action_id": "showcase_approval_deny_device",
"value": "click_me_123"
}
]
}
]
}
109 changes: 109 additions & 0 deletions apps/showcase-bot/src/messages/blocks/14-template-notification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"blocks": [
{
"type": "section",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Looks like you have a scheduling conflict with this event:"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*<fakeLink.toUserProfiles.com|Iris / Zelda 1-1>*\nTuesday, January 21 4:00-4:30pm\nBuilding 2 - Havarti Cheese (3)\n2 guests"
},
"accessory": {
"type": "image",
"image_url": "https://api.slack.com/img/blocks/bkb_template_images/notifications.png",
"alt_text": "calendar thumbnail"
}
},
{
"type": "context",
"elements": [
{
"type": "image",
"image_url": "https://api.slack.com/img/blocks/bkb_template_images/notificationsWarningIcon.png",
"alt_text": "notifications warning icon"
},
{
"type": "mrkdwn",
"text": "*Conflicts with Team Huddle: 4:15-4:30pm*"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Propose a new time:*"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Today - 4:30-5pm*\nEveryone is available: @iris, @zelda"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Choose"
},
"action_id": "showcase_notification_choose_today",
"value": "click_me_123"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Tomorrow - 4-4:30pm*\nEveryone is available: @iris, @zelda"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Choose"
},
"action_id": "showcase_notification_choose_tomorrow_4",
"value": "click_me_123"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Tomorrow - 6-6:30pm*\nSome people aren't available: @iris, ~@zelda~"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Choose"
},
"action_id": "showcase_notification_choose_tomorrow_6",
"value": "click_me_123"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*<fakelink.ToMoreTimes.com|Show more times>*"
}
}
]
}
Loading