Skip to content

Comments

Message shortcuts with multi-bot support and modal validation#13

Merged
tyom merged 22 commits intomainfrom
shortcuts
Feb 22, 2026
Merged

Message shortcuts with multi-bot support and modal validation#13
tyom merged 22 commits intomainfrom
shortcuts

Conversation

@tyom
Copy link
Owner

@tyom tyom commented Feb 22, 2026

  • Implement full Slack message shortcuts (message actions) with multi-bot support: shortcuts from all connected bots are aggregated and displayed in a "Connect to apps" submenu in both the three-dot dropdown menu and right-click context menu
  • Add targeted shortcut dispatch so each shortcut is sent only to the bot that registered it, rather than broadcasting to all connected bots
  • Fix shortcut payload type from shortcut to message_action to match the real Slack API
  • Improve modal fidelity: match real Slack modal width (520px), add required field validation with red borders and live revalidation after first submit, hide image collapse toggle in modals
  • Unify file image rendering with the ImageBlock component and fix shortcut image detection by including Block Kit image blocks in the shortcut payload
  • Add a showcase message shortcut to the demo bot for testing
  • Add DropdownMenu UI component set (bits-ui based)
  • Fix Electron drag region blocking the image preview close button

Summary by CodeRabbit

  • New Features

    • Message Context modal to view message metadata and attached images
    • New Slack message templates: approval, notification and vote
    • Expanded dropdown menu components and richer shortcut menus
  • Improvements

    • Live modal validation with per‑field error messages
    • Collapsible image previews and improved preview backdrop behaviour
    • Short timestamp format, smarter message grouping and bot avatar support
    • Markdown now auto‑prefixes protocol‑less links
  • Bug Fixes

    • More reliable file‑serving and image‑size handling
    • Shortcuts can be dispatched to the appropriate bot connection

tyom added 12 commits February 21, 2026 17:57
…to state

- Add optional icon_emoji and icon_url fields to SlackAppConfig.app
- Add getBotForShortcut(callbackId) method to EmulatorState for resolving shortcut ownership
…rgeted

- Return full shortcut arrays and icon fields (iconEmoji, iconUrl) per bot in getConnectedBots
- Resolve owning bot by callback_id via getBotForShortcut before dispatching shortcuts
- Add optional targetBotId parameter to dispatchShortcut with broadcast fallback
…context menu

- Change ConnectedBotInfo.shortcuts from number to Shortcut[] with iconEmoji/iconUrl fields
- Replace getMessageShortcut() with getAllMessageShortcuts() grouped by bot
- Export BotShortcutGroup type from barrel file
- Update SSE handler and bot type to pass full shortcut data and icon fields
- Replace hardcoded Generate Image menu item with grouped multi-bot submenu
- Remove onGenerateImage prop chain from Message, MessagePanel, and ThreadPanel
- Shortcuts dispatched directly from Message via triggerMessageShortcut
- Context menu works on all messages, not just image messages
- Each submenu item shows bot icon + shortcut name (bold) + app name (muted)
Add "Showcase message context" message shortcut to the showcase bot that
opens a modal showing message details including images from both file
attachments and Block Kit blocks.

Fix shortcut payload type from 'shortcut' to 'message_action' to match
Bolt's expected type for message shortcuts. Enrich shortcut payload with
full message data (text, files, blocks) from emulator state.
…utton

Add no-drag to ImagePreviewModal overlay so -webkit-app-region: drag
on headers behind the modal doesn't intercept mouse events.
Replace custom dropdown with shadcn-svelte DropdownMenu so the
hover menu matches the right-click context menu. Also fix submenu
close animation flash in both dropdown and context menu sub-content.
…e detection

- Reuse ImageBlock component for file-attached images instead of duplicate
  rendering code, giving consistent collapse triangle styling and size display
- Constrain file image thumbnails to max-height 360px to match real Slack
- Extract image URLs from Block Kit image blocks into files array when
  dispatching shortcuts so connected bots can detect them
- Fix image-size endpoint returning wrong size (35 B) for local files by
  supporting HEAD requests on file serving route and checking response status
- Add no-store cache headers to image-size responses
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

No actionable comments were generated in the recent review. 🎉

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f06a252 and c8f3994.

📒 Files selected for processing (5)
  • apps/showcase-bot/src/messages/blocks/13-template-approval.json
  • apps/showcase-bot/src/messages/blocks/15-template-vote.json
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
  • packages/mrkdwn/src/mrkdwn-to-html.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/showcase-bot/src/messages/blocks/13-template-approval.json
  • apps/showcase-bot/src/messages/blocks/15-template-vote.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.svelte

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.svelte: Use {@html} in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)
Use Svelte 5 runes ($state, $derived, $effect) for reactivity instead of legacy stores

Files:

  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
apps/ui/src/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (CLAUDE.md)

Use $lib path alias when importing from src/lib/ in Svelte applications

Files:

  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable names in TypeScript/JavaScript code
No semicolons in code, enforced by Prettier
Use single quotes instead of double quotes
Use trailing commas in es5 format
Prefix unused variables with underscore (_)

Files:

  • packages/mrkdwn/src/mrkdwn-to-html.ts
packages/mrkdwn/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Slack mrkdwn converter must provide bidirectional conversion (mrkdwn to HTML and Markdown to mrkdwn)

Files:

  • packages/mrkdwn/src/mrkdwn-to-html.ts
🧠 Learnings (5)
📚 Learning: 2026-02-22T01:14:27.310Z
Learnt from: tyom
Repo: tyom/botarium PR: 13
File: packages/slack/src/server/web-api.ts:1960-1973
Timestamp: 2026-02-22T01:14:27.310Z
Learning: The Botarium simulator (packages/slack/src/server) only supports single file uploads, so `storedMessage.file` (singular) is the correct data structure and can replace `message.files` when present.

Applied to files:

  • apps/ui/src/components/Message.svelte
📚 Learning: 2026-01-14T21:36:07.933Z
Learnt from: tyom
Repo: tyom/botarium PR: 1
File: apps/ui/src/components/MessagePanel.svelte:10-23
Timestamp: 2026-01-14T21:36:07.933Z
Learning: In the client-only Botarium UI (Vite + Svelte), do not add or rely on SSR-specific guards (e.g., checks for sessionStorage) in components. Since the app runs only in Electron or the browser, code that accesses browser APIs will execute in a non-SSR environment, so SSR-unsafe checks are unnecessary and can be removed for maintainability.

Applied to files:

  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to packages/mrkdwn/src/**/*.ts : Slack mrkdwn converter must provide bidirectional conversion (mrkdwn to HTML and Markdown to mrkdwn)

Applied to files:

  • packages/mrkdwn/src/mrkdwn-to-html.ts
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to **/*.svelte : Use `{html}` in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)

Applied to files:

  • packages/mrkdwn/src/mrkdwn-to-html.ts
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to apps/ui/src/**/*.{ts,tsx,svelte} : Use `$lib` path alias when importing from `src/lib/` in Svelte applications

Applied to files:

  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
🧬 Code graph analysis (1)
packages/mrkdwn/src/mrkdwn-to-html.ts (1)
packages/mrkdwn/src/index.ts (1)
  • mrkdwnToHtml (1-1)
🔇 Additional comments (10)
apps/ui/src/components/blockkit/blocks/SectionBlock.svelte (2)

12-12: LGTM!

Import path correctly updated to use the $lib alias as per the coding guidelines for Svelte applications.


40-40: LGTM!

The { useBr: true } option is applied consistently to both block.text and block.fields rendering, ensuring uniform line-break handling. The class addition on line 46 aligns with the existing styling pattern on line 39, maintaining visual consistency.

Also applies to: 46-47

packages/mrkdwn/src/mrkdwn-to-html.ts (2)

37-45: Protocol-less link handling looks sound.

The regex correctly excludes Slack mentions (@, #, !) via the [^@#!] negative character class and requires a dot in the URL portion, which is a sensible constraint. The processing order (protocol-based links first) ensures no conflicts.

Minor edge case: patterns like <a.> would technically match, but this is unlikely in real mrkdwn content and the trade-off for simpler regex is reasonable.


102-119: Backward-compatible API extension with configurable BR rendering.

The optional options parameter with useBr?: boolean maintains backward compatibility whilst enabling flexible line-break rendering—<br> for compact blocks or a styled span for messages. Clean implementation.

apps/ui/src/components/Message.svelte (6)

9-83: Good adoption of shortcut aggregation and grouped timestamp derivations.

The new shortcut state and grouped timestamp handling look clean and consistent with Svelte 5 runes and the existing $lib aliasing conventions.


336-352: Shortcut item snippet reads well.

The extracted snippet improves reuse and keeps the menu item layout consistent.


370-417: Dropdown/context menu parity looks solid.

Both menu surfaces now mirror the shortcuts submenu and delete handling cleanly.

Also applies to: 566-593


419-463: Grouped vs non‑grouped header rendering is clear.

The timestamp/identity switch is tidy and should read well in grouped threads.


486-509: ImageBlock reuse for file previews is consistent.

Nice consolidation of image rendering and preview wiring.


323-328: Shortcut routing uses bot discrimination correctly.

The dispatcher looks up which bot owns the callback_id by checking all connected bots' appConfigs. The getBotForShortcut() function iterates through registered bots and finds the one containing the matching callback in its shortcuts array, then dispatches to that specific bot. No ambiguity exists—each bot's registered shortcuts are scoped to that bot's appConfig.


📝 Walkthrough

Walkthrough

Adds a message-action shortcut for the showcase bot and its handler to open a "Message Context" modal; propagates richer shortcut and icon metadata through UI state/types and server APIs; refactors message UI to use a DropdownMenu/ImageBlock system and grouping logic; adds client-side modal validation and collapsible image controls; and enables targeted server-side shortcut dispatch.

Changes

Cohort / File(s) Summary
Showcase bot shortcut
apps/showcase-bot/config.yaml, apps/showcase-bot/src/listeners/index.ts, apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
Adds a message-action shortcut entry and registers a shortcut handler that gathers message context (ts, channel, user, text, files/blocks/images) and opens a "Message Context" modal.
UI state, dispatcher & types
apps/ui/src/lib/types.ts, apps/ui/src/lib/dispatcher.svelte.ts, apps/ui/src/lib/state.svelte.ts, apps/ui/src/lib/state/bots.svelte.ts
ConnectedBotInfo.shortcuts becomes Shortcut[] and gains iconEmoji/iconUrl; removed older getShortcut/getMessageShortcut exports and added getAllMessageShortcuts/BotShortcutGroup; dispatcher/state updated to carry richer shortcut/icon metadata.
Message UI & grouping
apps/ui/src/components/Message.svelte, apps/ui/src/components/MessagePanel.svelte, apps/ui/src/components/ThreadPanel.svelte
Refactors message rendering: introduces isGrouped/short timestamp handling, replaces inline image/menu logic with ImageBlock and DropdownMenu components, centralises shortcut triggering via new shortcut APIs, and removes legacy image-generation shortcut flow.
Modal validation & BlockKit changes
apps/ui/src/components/ModalOverlay.svelte, apps/ui/src/components/blockkit/BlockKitRenderer.svelte, apps/ui/src/components/blockkit/blocks/InputBlock.svelte, apps/ui/src/components/blockkit/blocks/ImageBlock.svelte
Adds client-side modal validation with per-block validationErrors and hasSubmitted flow; BlockKitRenderer accepts errors and imageCollapsible; InputBlock shows explicit error UI; ImageBlock gains collapsible prop to toggle size/toggle UI.
Dropdown menu library
apps/ui/src/lib/components/ui/dropdown-menu/*, apps/ui/src/lib/components/ui/dropdown-menu/index.ts
Introduces a full set of DropdownMenu wrapper components (Root, Sub, Content, Item, Group, Checkbox/Radio items & groups, Separator, Shortcut, Trigger, SubContent, SubTrigger, GroupHeading, Portal) that wrap bits‑ui primitives with styling and data-slot attributes.
Context menu animation
apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte
Removed closing-state animation classes from SubContent while keeping opening-state animations.
Small UI tweaks
apps/ui/src/components/ImagePreviewModal.svelte, apps/ui/src/components/BotAboutHeader.svelte, apps/ui/src/components/Sidebar.svelte
Added backdrop no-drag class; bot header and sidebar now render iconUrl image when provided, falling back to the placeholder avatar.
Time utilities
apps/ui/src/lib/time.ts
Adds formatTimestampShort and isWithinMinutes utilities used for short timestamp display and message grouping.
Server: shortcut dispatch & image endpoints
packages/slack/src/server/socket-mode.ts, packages/slack/src/server/state.ts, packages/slack/src/server/types.ts, packages/slack/src/server/web-api.ts, packages/slack/src/server/index.ts
Adds EmulatorState.getBotForShortcut; changes shortcut payloads to message_action including blocks/files; web-api resolves owning bot for callback_id and dispatches to target bot; socket-mode.dispatchShortcut accepts optional targetBotId to send to a single connection; image-size endpoint treats HEAD like GET and adds no-store cache headers.
Server: public API shape change
packages/slack/src/server/web-api.ts
getConnectedBots now returns shortcuts as arrays (not counts) and includes iconEmoji/iconUrl in bot objects.
Block rendering & mrkdwn
apps/ui/src/components/blockkit/SectionBlock.svelte, apps/ui/src/components/blockkit/context.ts, packages/mrkdwn/src/mrkdwn-to-html.ts, packages/mrkdwn/src/mrkdwn-to-html.test.ts
renderMrkdwn gains an options param (useBr) and plain_text escaping; mrkdwnToHtml supports protocol-less domain links (auto-prefixes https) and useBr option; tests added for protocol-less links.
Showcase message templates
apps/showcase-bot/src/messages/blocks/13-template-approval.json, .../14-template-notification.json, .../15-template-vote.json
Adds three new Slack Block Kit JSON templates for approval, notification and voting examples.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Message Component
    participant ClientState as Client Dispatcher/State
    participant WebAPI as Server Web API
    participant Socket as SocketModeServer
    participant Bot as Connected Bot

    User->>UI: Invoke message action "Showcase message context"
    UI->>UI: Collect message context (ts, channel, user, text, files, blocks)
    UI->>ClientState: triggerMessageShortcut(callback_id, context)
    ClientState->>WebAPI: POST shortcut (callback_id, trigger_id, message payload)
    WebAPI->>WebAPI: getBotForShortcut(callback_id) -> targetBot
    WebAPI->>Socket: dispatchShortcut(message_action payload, targetBotId)
    Socket->>Bot: Send envelope to target bot connection
    Bot->>User: Open modal with assembled message context
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarises the main changes: implementing Slack message shortcuts with multi-bot support and adding modal validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch shortcuts

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/ui/src/components/MessagePanel.svelte (1)

42-50: ⚠️ Potential issue | 🟡 Minor

Use $lib alias for lib imports.
This import targets src/lib, so switch to $lib to keep paths stable.

Suggested change
-  import { clearChannelMessages, deleteMessage } from '../lib/dispatcher.svelte'
+  import { clearChannelMessages, deleteMessage } from '$lib/dispatcher.svelte'

As per coding guidelines, apps/ui/src/**/*.{ts,tsx,svelte}: Use $lib path alias when importing from src/lib/ in Svelte applications.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/MessagePanel.svelte` around lines 42 - 50, The import
from '../lib/dispatcher.svelte' in MessagePanel.svelte should use the Svelte
$lib alias instead of a relative path; update the import that brings in
clearChannelMessages and deleteMessage to '$lib/dispatcher.svelte' so the module
resolution is stable and matches the project's apps/ui/src/**/*.{ts,tsx,svelte}
guideline.
apps/ui/src/components/blockkit/blocks/InputBlock.svelte (1)

2-20: ⚠️ Potential issue | 🟡 Minor

Use $lib alias for lib imports.
The types import targets src/lib, so it should use the $lib alias.

Suggested change
-  } from '../../../lib/types'
+  } from '$lib/types'

As per coding guidelines, apps/ui/src/**/*.{ts,tsx,svelte}: Use $lib path alias when importing from src/lib/ in Svelte applications.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/blockkit/blocks/InputBlock.svelte` around lines 2 -
20, The import of many types (e.g., SlackInputBlock, SlackPlainTextInputElement,
SlackStaticSelectElement, SlackFileInputElement, SlackCheckboxesElement,
SlackNumberInputElement, SlackEmailInputElement, SlackUrlInputElement,
SlackRadioButtonsElement, SlackDatePickerElement, SlackTimePickerElement,
SlackDateTimePickerElement, SlackWorkspaceSelectElement, SlackOption,
UploadedFile) currently uses a relative path ('../../../lib/types'); update that
import to use the Svelte $lib alias (import from '$lib/types') so it follows the
apps/ui Svelte path-alias guideline and resolves the src/lib module correctly.
apps/ui/src/lib/state/bots.svelte.ts (1)

6-14: ⚠️ Potential issue | 🟡 Minor

Use the $lib alias for src/lib imports.
Please switch these imports to the alias to match the project convention.

Proposed fix
-import type {
-  SlashCommand,
-  Shortcut,
-  SlackView,
-  SlackAppConfig,
-  ConnectedBotInfo,
-} from '../types'
-import { BOT_USER_ID } from '../types'
+import type {
+  SlashCommand,
+  Shortcut,
+  SlackView,
+  SlackAppConfig,
+  ConnectedBotInfo,
+} from '$lib/types'
+import { BOT_USER_ID } from '$lib/types'

As per coding guidelines: apps/ui/src/**/*.{ts,tsx,svelte}: Use $lib path alias when importing from src/lib/ in Svelte applications.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/lib/state/bots.svelte.ts` around lines 6 - 14, The imports at the
top of bots.svelte.ts currently reference relative paths from src/lib; change
them to use the Svelte $lib alias (e.g., replace imports that bring in
SlashCommand, Shortcut, SlackView, SlackAppConfig, ConnectedBotInfo, BOT_USER_ID
and simulatorState from '../types' and '../state.svelte' to import from
'$lib/types' and '$lib/state.svelte' respectively) so the file follows the
project's `$lib` import convention.
apps/ui/src/components/Message.svelte (1)

555-564: ⚠️ Potential issue | 🟡 Minor

Same trailing separator issue in ContextMenu.

The ContextMenu has the identical problem: the separator on line 557 renders even when onDelete is undefined.

🛠️ Proposed fix
           </ContextMenu.SubContent>
         </ContextMenu.Sub>
-        <ContextMenu.Separator />
       {/if}
       {`#if` onDelete}
+        {`#if` hasShortcuts}
+          <ContextMenu.Separator />
+        {/if}
         <ContextMenu.Item variant="destructive" onclick={handleDelete}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/Message.svelte` around lines 555 - 564, The trailing
ContextMenu.Separator is rendered unconditionally and shows even when onDelete
is undefined; update the JSX in Message.svelte so the separator is only rendered
when onDelete is truthy — e.g., move or wrap <ContextMenu.Separator /> inside
the same conditional that renders the destructive <ContextMenu.Item> (the block
using onDelete, handleDelete, and <ContextMenu.Item variant="destructive">) so
the separator and the delete item are gated together.
🧹 Nitpick comments (1)
apps/ui/src/components/Message.svelte (1)

358-398: Consider extracting duplicated shortcut menu rendering into a Svelte snippet.

The shortcut group iteration (icon, name, bot label) is repeated almost identically in both DropdownMenu and ContextMenu. Svelte 5 snippets could reduce this duplication and simplify future changes.

Also applies to: 532-564

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/Message.svelte` around lines 358 - 398, Extract the
repeated shortcut-group rendering into a reusable Svelte snippet/component and
use it in both DropdownMenu and ContextMenu to remove duplication: create a
small component/snippet (e.g., ShortcutList or ShortcutItem) that accepts
shortcutGroups and handleShortcut as props and renders the {`#each`
group.shortcuts as shortcut (shortcut.callback_id)} loop with the same markup
(using group.botIcon, group.botName.charAt(0).toUpperCase(), shortcut.name, and
shortcut.callback_id for keys), then replace the inline loops in the
DropdownMenu.SubContent and the ContextMenu content with that snippet/component
to centralize markup and behavior.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbe2493 and d393936.

📒 Files selected for processing (39)
  • apps/showcase-bot/config.yaml
  • apps/showcase-bot/src/listeners/index.ts
  • apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
  • apps/ui/src/components/ImagePreviewModal.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/ModalOverlay.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/blockkit/BlockKitRenderer.svelte
  • apps/ui/src/components/blockkit/blocks/ImageBlock.svelte
  • apps/ui/src/components/blockkit/blocks/InputBlock.svelte
  • apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/index.ts
  • apps/ui/src/lib/dispatcher.svelte.ts
  • apps/ui/src/lib/state.svelte.ts
  • apps/ui/src/lib/state/bots.svelte.ts
  • apps/ui/src/lib/types.ts
  • packages/slack/src/server/index.ts
  • packages/slack/src/server/socket-mode.ts
  • packages/slack/src/server/state.ts
  • packages/slack/src/server/types.ts
  • packages/slack/src/server/web-api.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable names in TypeScript/JavaScript code
No semicolons in code, enforced by Prettier
Use single quotes instead of double quotes
Use trailing commas in es5 format
Prefix unused variables with underscore (_)

Files:

  • packages/slack/src/server/types.ts
  • apps/ui/src/lib/state.svelte.ts
  • apps/ui/src/lib/types.ts
  • packages/slack/src/server/state.ts
  • packages/slack/src/server/socket-mode.ts
  • apps/ui/src/lib/state/bots.svelte.ts
  • apps/ui/src/lib/dispatcher.svelte.ts
  • packages/slack/src/server/web-api.ts
  • apps/showcase-bot/src/listeners/index.ts
  • apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
  • packages/slack/src/server/index.ts
  • apps/ui/src/lib/components/ui/dropdown-menu/index.ts
packages/*/src/server/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

packages/*/src/server/*.ts: Platform emulator plugins must implement WebSocket protocol for event broadcasting to the frontend
Organize platform plugin packages with server/state.ts for in-memory state, server/web-api.ts for API handlers, and server/persistence.ts for storage

Files:

  • packages/slack/src/server/types.ts
  • packages/slack/src/server/state.ts
  • packages/slack/src/server/socket-mode.ts
  • packages/slack/src/server/web-api.ts
  • packages/slack/src/server/index.ts
packages/slack/src/server/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use SSE (Server-Sent Events) for broadcasting events from the emulator to the frontend

Files:

  • packages/slack/src/server/types.ts
  • packages/slack/src/server/state.ts
  • packages/slack/src/server/socket-mode.ts
  • packages/slack/src/server/web-api.ts
  • packages/slack/src/server/index.ts
**/*.svelte

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.svelte: Use {@html} in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)
Use Svelte 5 runes ($state, $derived, $effect) for reactivity instead of legacy stores

Files:

  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
  • apps/ui/src/components/ImagePreviewModal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
  • apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte
  • apps/ui/src/components/blockkit/blocks/InputBlock.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/blockkit/blocks/ImageBlock.svelte
  • apps/ui/src/components/blockkit/BlockKitRenderer.svelte
  • apps/ui/src/components/ModalOverlay.svelte
  • apps/ui/src/components/Message.svelte
apps/ui/src/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (CLAUDE.md)

Use $lib path alias when importing from src/lib/ in Svelte applications

Files:

  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
  • apps/ui/src/lib/state.svelte.ts
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
  • apps/ui/src/lib/types.ts
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
  • apps/ui/src/components/ImagePreviewModal.svelte
  • apps/ui/src/lib/state/bots.svelte.ts
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/lib/dispatcher.svelte.ts
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
  • apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte
  • apps/ui/src/components/blockkit/blocks/InputBlock.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/blockkit/blocks/ImageBlock.svelte
  • apps/ui/src/components/blockkit/BlockKitRenderer.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/index.ts
  • apps/ui/src/components/ModalOverlay.svelte
  • apps/ui/src/components/Message.svelte
packages/slack/src/server/socket-mode.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Slack API emulator must implement Socket Mode protocol for bot communication

Files:

  • packages/slack/src/server/socket-mode.ts
🧠 Learnings (4)
📚 Learning: 2026-01-14T21:36:07.933Z
Learnt from: tyom
Repo: tyom/botarium PR: 1
File: apps/ui/src/components/MessagePanel.svelte:10-23
Timestamp: 2026-01-14T21:36:07.933Z
Learning: In the client-only Botarium UI (Vite + Svelte), do not add or rely on SSR-specific guards (e.g., checks for sessionStorage) in components. Since the app runs only in Electron or the browser, code that accesses browser APIs will execute in a non-SSR environment, so SSR-unsafe checks are unnecessary and can be removed for maintainability.

Applied to files:

  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
  • apps/ui/src/components/ImagePreviewModal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
  • apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte
  • apps/ui/src/components/blockkit/blocks/InputBlock.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
  • apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/blockkit/blocks/ImageBlock.svelte
  • apps/ui/src/components/blockkit/BlockKitRenderer.svelte
  • apps/ui/src/components/ModalOverlay.svelte
  • apps/ui/src/components/Message.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to packages/slack/src/server/socket-mode.ts : Slack API emulator must implement Socket Mode protocol for bot communication

Applied to files:

  • packages/slack/src/server/state.ts
  • packages/slack/src/server/socket-mode.ts
  • packages/slack/src/server/web-api.ts
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to packages/slack/src/server/**/*.ts : Use SSE (Server-Sent Events) for broadcasting events from the emulator to the frontend

Applied to files:

  • packages/slack/src/server/socket-mode.ts
  • apps/ui/src/lib/dispatcher.svelte.ts
  • packages/slack/src/server/index.ts
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to apps/ui/src/**/*.{ts,tsx,svelte} : Use `$lib` path alias when importing from `src/lib/` in Svelte applications

Applied to files:

  • apps/ui/src/lib/components/ui/dropdown-menu/index.ts
🧬 Code graph analysis (5)
apps/ui/src/lib/types.ts (1)
apps/showcase-bot/src/config/loader.ts (1)
  • Shortcut (70-75)
packages/slack/src/server/state.ts (1)
packages/slack/src/server/types.ts (1)
  • ConnectedBot (354-360)
packages/slack/src/server/socket-mode.ts (2)
packages/slack/src/server/types.ts (1)
  • MessageShortcutPayload (125-140)
packages/slack/src/lib/logger.ts (1)
  • socketModeLogger (35-35)
apps/ui/src/lib/state/bots.svelte.ts (1)
apps/ui/src/lib/types.ts (1)
  • Shortcut (54-59)
packages/slack/src/server/web-api.ts (2)
packages/slack/src/server/index.ts (1)
  • message (565-567)
packages/slack/src/lib/logger.ts (1)
  • webApiLogger (34-34)
🔇 Additional comments (43)
apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte (1)

1-14: Looks good — clean wrapper and props forwarding.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte (1)

1-20: LGTM!

Clean wrapper component following established patterns. Uses Svelte 5 runes ($bindable, $props) correctly, properly imports via the $lib alias as per coding guidelines, and the Tailwind v4 classes including origin-(--bits-dropdown-menu-content-transform-origin) are correctly formatted.

apps/ui/src/lib/components/ui/context-menu/context-menu-sub-content.svelte (1)

1-20: LGTM!

The component correctly uses Svelte 5 runes ($props, $bindable) and the $lib path alias as per coding guidelines. The removal of closing-state animations (animate-out, fade-out-0, zoom-out-95) is a valid UX choice that can make submenu transitions feel snappier and avoid potential animation timing conflicts.

apps/showcase-bot/src/listeners/index.ts (1)

4-9: Shortcut listener registration is correctly wired.

Nice, clean addition that aligns with existing listener setup.

apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts (1)

28-63: Image extraction from files and Block Kit blocks looks solid.

Good coverage for both attachment files and Block Kit image blocks.

apps/showcase-bot/config.yaml (1)

14-18: Shortcut configuration matches the handler.

Looks consistent with the new listener.

apps/ui/src/components/ImagePreviewModal.svelte (1)

202-202: LGTM!

The no-drag class addition correctly prevents Electron's drag region from interfering with the modal backdrop interactions, aligning with the PR objective to fix the image preview close button issue.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte (1)

1-16: LGTM!

Clean wrapper component following the established pattern. Properly uses Svelte 5 runes with $bindable for two-way binding of ref and value, and correctly forwards all remaining props to the primitive.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte (1)

1-10: LGTM!

Minimal and clean root wrapper component. Correctly uses $bindable(false) for the open state and properly forwards all other props to the primitive.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte (1)

1-17: LGTM!

Well-structured separator component with sensible default styling. Correctly uses the $lib path alias for the cn utility import and properly merges custom class names.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte (1)

1-7: LGTM!

Minimal portal wrapper that correctly forwards all props to the underlying primitive. No additional logic needed for this pass-through component.

packages/slack/src/server/index.ts (3)

472-475: LGTM!

Adding HEAD method support for the file serving endpoint is appropriate. This allows clients to check file metadata without downloading the full file body.


500-520: Good defensive fix for HEAD response handling.

The change to check res.ok before extracting content-length (line 504) correctly handles cases where the HEAD request fails with an error status code. Previously, a failed HEAD response could still return a content-length header, leading to incorrect size values.

The addition of Cache-Control: no-store is appropriate for this dynamic proxy endpoint.


521-531: LGTM!

Consistent error response formatting with the Cache-Control: no-store header to prevent caching of error states.

packages/slack/src/server/state.ts (1)

928-939: LGTM!

The getBotForShortcut method follows the established pattern of getBotForCommand (lines 918-926). It correctly filters for connected bots and uses efficient short-circuit evaluation with .some() to find the matching shortcut by callback_id.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte (1)

1-29: LGTM!

Well-structured submenu trigger component with comprehensive styling for various states (highlighted, open, disabled). The chevron icon correctly indicates a submenu, and the optional inset prop provides layout flexibility. Properly uses the $lib path alias and Svelte 5 runes.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte (1)

1-16: LGTM!

Clean wrapper component following the established bits-ui pattern. Correctly uses Svelte 5 runes ($bindable, $props) and maintains consistency with the other dropdown menu components.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte (1)

1-14: LGTM!

Consistent wrapper implementation matching the established component pattern. Proper use of Svelte 5 runes and data-slot attribution.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte (1)

1-10: LGTM!

Minimal wrapper for submenu state management. The bindable open prop with default false enables proper two-way binding for submenu visibility control.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte (1)

1-33: LGTM!

Well-structured radio item component with proper checked state indication using the CircleIcon. The snippet pattern for conditional rendering ({#snippet children} with @render) follows Svelte 5 conventions correctly.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte (1)

1-22: LGTM!

Clean implementation with the inset prop providing flexible padding control via the data-[inset]:ps-8 Tailwind variant. The type composition using ComponentProps<typeof ...> & { inset?: boolean } is a good pattern for extending primitive props.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte (1)

1-31: LGTM!

Proper composition of portal and content primitives. The Tailwind v4 syntax for CSS variables (max-h-(--bits-dropdown-menu-content-available-height)) and outline-hidden are correctly applied.

packages/slack/src/server/types.ts (2)

92-93: LGTM!

The icon_emoji and icon_url fields align with Slack's bot configuration API, enabling proper bot icon display in the multi-bot shortcuts UI.


125-140: Payload type correction is accurate.

Changing type to 'message_action' correctly aligns with @slack/bolt's MessageShortcut interface as noted in the comment. The addition of optional blocks enables passing Block Kit content through the shortcut payload.

apps/ui/src/lib/types.ts (1)

576-586: Type changes are consistent with the PR objectives.

The shortcuts field change from number to Shortcut[] enables the UI to display full shortcut details (name, description, callback_id) in the multi-bot submenu. All consumers—including the filtering logic in getAllMessageShortcuts() and component iterations in Message.svelte—properly handle the new array type. The iconEmoji and iconUrl fields use frontend camelCase convention, correctly mapping from the backend's icon_emoji/icon_url.

apps/ui/src/lib/state.svelte.ts (1)

102-116: Export surface update looks sound.

apps/ui/src/components/ThreadPanel.svelte (1)

93-95: Message rendering update looks good.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte (1)

1-20: Neat, minimal wrapper component.

apps/ui/src/components/blockkit/blocks/InputBlock.svelte (1)

95-199: Error state wiring and visuals look good.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte (1)

1-24: Clean label wrapper, nicely aligned with the other primitives.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte (1)

1-43: Solid checkbox item wrapper.

apps/ui/src/components/blockkit/blocks/ImageBlock.svelte (1)

9-65: Collapsible handling looks sound.
The gated fetch and conditional UI keep the preview behaviour consistent across collapsible and non‑collapsible contexts.

apps/ui/src/components/blockkit/BlockKitRenderer.svelte (1)

52-103: Error propagation and image‑collapsibility wiring look good.
Passing per‑block errors and the collapsible flag through to the child blocks is tidy and consistent.

apps/ui/src/components/ModalOverlay.svelte (2)

39-248: Validation flow is cohesive.
Required‑field detection plus live revalidation after the first submit reads cleanly.


323-354: Modal layout and renderer wiring look good.
The width adjustment and passing errors/imageCollapsible into BlockKitRenderer are neatly integrated.

apps/ui/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte (1)

1-27: Dropdown menu item wrapper looks good.
Prop forwarding and class composition are clear and consistent.

apps/ui/src/lib/dispatcher.svelte.ts (1)

294-306: Bot shortcut metadata propagation looks good.
Icons and shortcut arrays are now surfaced consistently in the SSE payload.

Also applies to: 423-436

packages/slack/src/server/web-api.ts (1)

1409-1413: Connected‑bot payload enrichment looks good.
Including icon fields and shortcut arrays in the response aligns cleanly with the updated UI.

apps/ui/src/lib/components/ui/dropdown-menu/index.ts (1)

1-54: Dropdown menu re‑exports look tidy.
The consolidated export surface is clear for consumers.

apps/ui/src/components/Message.svelte (4)

319-325: LGTM!

The handleShortcut function correctly dispatches the shortcut with the required message context (ts, text, file).


453-472: LGTM!

Good approach reusing ImageBlock for file attachments. The inline block object construction is clean and the max-height constraint via Tailwind is appropriate for thumbnails.


69-70: LGTM!

Proper use of Svelte 5 $derived runes for reactive shortcut state.


39-40: LGTM!

Imports correctly use the $lib path alias as per coding guidelines.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts`:
- Around line 13-26: The section block creation in blocks uses message.text
directly which can exceed Slack's 3000-char mrkdwn limit and cause views.open to
fail; before building fields/blocks (where message.text is referenced), truncate
message.text to at most 3000 characters (preserving the code-fence/backtick
formatting if you wrap it with ```...```), then use the truncated value when
constructing fields and blocks (keep the existing '_empty_' fallback), and
ensure the final mrkdwn string passed into blocks (in the map that builds
KnownBlock[]) is under 3000 chars so views.open cannot fail due to oversized
text.

In `@apps/ui/src/components/Message.svelte`:
- Around line 386-398: The DropdownMenu.Separator is rendered unconditionally
which leaves an orphaned divider when hasShortcuts is true but onDelete is
falsy; update the conditional rendering so the separator
(DropdownMenu.Separator) is only included when there will be a following item
(i.e., wrap or gate the Separator with the same condition that renders the
Delete item or change the outer condition to require both hasShortcuts and
onDelete), ensuring handleDelete / onDelete logic remains unchanged.

In `@apps/ui/src/components/ThreadPanel.svelte`:
- Around line 1-5: Update the imports in ThreadPanel.svelte to use the $lib
alias instead of a relative path: replace imports that reference
'../lib/dispatcher.svelte' (including MockApp type, deleteMessage, sendMessage)
with imports from '$lib/dispatcher.svelte' so the file follows the Svelte app
aliasing convention; ensure any other references to '../lib' in this file are
similarly switched to '$lib'.

In `@packages/slack/src/server/socket-mode.ts`:
- Around line 705-723: The code silently returns when a bot is marked
'connected' but this.connections.get(bot.connectionId) returns falsy; add a
warning log in that branch to surface missing active socket details. Inside the
targetBotId handling block (where you call this.state.getBot(targetBotId) and
then const conn = this.connections.get(bot.connectionId)), when conn is falsy
emit socketModeLogger.warn including targetBotId, bot.connectionId,
envelope.envelope_id and payload.callback_id so operators can see why the
shortcut was dropped; keep the existing early return behavior after logging.

In `@packages/slack/src/server/web-api.ts`:
- Around line 1960-2008: When building the files array in the Block Kit image
handling loop (see storedMessage, files, and the loop over
storedMessage.blocks), do not hard-code mimetype to 'image/png'; instead either
omit the mimetype field for these entries or attempt to infer it from the
image_url (e.g., via file extension or a content-type HEAD request) so the
metadata is accurate before calling this.socketMode.dispatchShortcut with the
constructed message; update the block branch that currently pushes { mimetype:
'image/png', url_private: b.image_url } to push either { url_private:
b.image_url } or an inferred mimetype when available.

---

Outside diff comments:
In `@apps/ui/src/components/blockkit/blocks/InputBlock.svelte`:
- Around line 2-20: The import of many types (e.g., SlackInputBlock,
SlackPlainTextInputElement, SlackStaticSelectElement, SlackFileInputElement,
SlackCheckboxesElement, SlackNumberInputElement, SlackEmailInputElement,
SlackUrlInputElement, SlackRadioButtonsElement, SlackDatePickerElement,
SlackTimePickerElement, SlackDateTimePickerElement, SlackWorkspaceSelectElement,
SlackOption, UploadedFile) currently uses a relative path
('../../../lib/types'); update that import to use the Svelte $lib alias (import
from '$lib/types') so it follows the apps/ui Svelte path-alias guideline and
resolves the src/lib module correctly.

In `@apps/ui/src/components/Message.svelte`:
- Around line 555-564: The trailing ContextMenu.Separator is rendered
unconditionally and shows even when onDelete is undefined; update the JSX in
Message.svelte so the separator is only rendered when onDelete is truthy — e.g.,
move or wrap <ContextMenu.Separator /> inside the same conditional that renders
the destructive <ContextMenu.Item> (the block using onDelete, handleDelete, and
<ContextMenu.Item variant="destructive">) so the separator and the delete item
are gated together.

In `@apps/ui/src/components/MessagePanel.svelte`:
- Around line 42-50: The import from '../lib/dispatcher.svelte' in
MessagePanel.svelte should use the Svelte $lib alias instead of a relative path;
update the import that brings in clearChannelMessages and deleteMessage to
'$lib/dispatcher.svelte' so the module resolution is stable and matches the
project's apps/ui/src/**/*.{ts,tsx,svelte} guideline.

In `@apps/ui/src/lib/state/bots.svelte.ts`:
- Around line 6-14: The imports at the top of bots.svelte.ts currently reference
relative paths from src/lib; change them to use the Svelte $lib alias (e.g.,
replace imports that bring in SlashCommand, Shortcut, SlackView, SlackAppConfig,
ConnectedBotInfo, BOT_USER_ID and simulatorState from '../types' and
'../state.svelte' to import from '$lib/types' and '$lib/state.svelte'
respectively) so the file follows the project's `$lib` import convention.

---

Nitpick comments:
In `@apps/ui/src/components/Message.svelte`:
- Around line 358-398: Extract the repeated shortcut-group rendering into a
reusable Svelte snippet/component and use it in both DropdownMenu and
ContextMenu to remove duplication: create a small component/snippet (e.g.,
ShortcutList or ShortcutItem) that accepts shortcutGroups and handleShortcut as
props and renders the {`#each` group.shortcuts as shortcut (shortcut.callback_id)}
loop with the same markup (using group.botIcon,
group.botName.charAt(0).toUpperCase(), shortcut.name, and shortcut.callback_id
for keys), then replace the inline loops in the DropdownMenu.SubContent and the
ContextMenu content with that snippet/component to centralize markup and
behavior.

tyom added 4 commits February 22, 2026 00:21
Show icon_url in message avatars, sidebar, and DM header
instead of the hardcoded Sparkles icon. Falls back to
Sparkles when no icon is provided.
Consecutive messages from the same user within 10 minutes are visually
grouped — hiding the avatar and name, reducing vertical padding, and
showing a 24h timestamp on hover in the avatar gutter.
- Truncate message text to stay within Slack's 3000-char mrkdwn limit
  in showcase shortcut modal
- Gate menu separators on onDelete to prevent orphaned dividers
- Add warning log when bot socket is missing during shortcut dispatch
- Infer image mimetype from URL extension instead of hard-coding png
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ui/src/components/Message.svelte (1)

9-39: ⚠️ Potential issue | 🟡 Minor

Use $lib alias for src/lib imports.

These imports should use the $lib alias instead of relative paths.

♻️ Suggested change
-  } from '../lib/types'
+  } from '$lib/types'
...
-  } from '../lib/state.svelte'
+  } from '$lib/state.svelte'
...
-  } from '../lib/dispatcher.svelte'
+  } from '$lib/dispatcher.svelte'
...
-  } from '../lib/time'
+  } from '$lib/time'

As per coding guidelines, apps/ui/src/**/*.{ts,tsx,svelte}: Use $lib path alias when importing from src/lib/ in Svelte applications.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/Message.svelte` around lines 9 - 39, Replace the
relative imports that target the app's src/lib directory with the $lib path
alias: update the imports that currently reference '../lib/types' (symbols:
SimulatorMessage, Shortcut, SlackBlock, SlackOption, SlackSectionBlock,
SlackActionsBlock, SlackContextActionsBlock, SlackInputBlock),
'../lib/state.svelte' (functions: getAllMessageShortcuts, getChannelDisplayName,
simulatorState, isBotUserId, getBotByUserId), '../lib/dispatcher.svelte'
(sendMessageBlockAction, triggerMessageShortcut), and '../lib/time'
(formatTimestamp, formatTimestampShort, formatRelativeTime, formatFullDate) to
use '$lib/...' instead; leave local component imports like
'./blockkit/BlockKitRenderer.svelte', './blockkit/blocks/ImageBlock.svelte', and
'./blockkit/context' unchanged. Ensure only the path prefix changes so imports
resolve via the $lib alias.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d393936 and 99f0ccd.

📒 Files selected for processing (9)
  • apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
  • apps/ui/src/components/BotAboutHeader.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/Sidebar.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/lib/time.ts
  • packages/slack/src/server/socket-mode.ts
  • packages/slack/src/server/web-api.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/slack/src/server/socket-mode.ts
  • apps/showcase-bot/src/listeners/shortcuts/showcase-shortcuts.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.svelte

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.svelte: Use {@html} in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)
Use Svelte 5 runes ($state, $derived, $effect) for reactivity instead of legacy stores

Files:

  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/BotAboutHeader.svelte
  • apps/ui/src/components/Sidebar.svelte
apps/ui/src/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (CLAUDE.md)

Use $lib path alias when importing from src/lib/ in Svelte applications

Files:

  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/lib/time.ts
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/BotAboutHeader.svelte
  • apps/ui/src/components/Sidebar.svelte
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable names in TypeScript/JavaScript code
No semicolons in code, enforced by Prettier
Use single quotes instead of double quotes
Use trailing commas in es5 format
Prefix unused variables with underscore (_)

Files:

  • apps/ui/src/lib/time.ts
  • packages/slack/src/server/web-api.ts
packages/*/src/server/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

packages/*/src/server/*.ts: Platform emulator plugins must implement WebSocket protocol for event broadcasting to the frontend
Organize platform plugin packages with server/state.ts for in-memory state, server/web-api.ts for API handlers, and server/persistence.ts for storage

Files:

  • packages/slack/src/server/web-api.ts
packages/slack/src/server/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use SSE (Server-Sent Events) for broadcasting events from the emulator to the frontend

Files:

  • packages/slack/src/server/web-api.ts
🧠 Learnings (3)
📚 Learning: 2026-01-14T21:36:07.933Z
Learnt from: tyom
Repo: tyom/botarium PR: 1
File: apps/ui/src/components/MessagePanel.svelte:10-23
Timestamp: 2026-01-14T21:36:07.933Z
Learning: In the client-only Botarium UI (Vite + Svelte), do not add or rely on SSR-specific guards (e.g., checks for sessionStorage) in components. Since the app runs only in Electron or the browser, code that accesses browser APIs will execute in a non-SSR environment, so SSR-unsafe checks are unnecessary and can be removed for maintainability.

Applied to files:

  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/BotAboutHeader.svelte
  • apps/ui/src/components/Sidebar.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to apps/ui/src/**/*.{ts,tsx,svelte} : Use `$lib` path alias when importing from `src/lib/` in Svelte applications

Applied to files:

  • apps/ui/src/components/ThreadPanel.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to packages/slack/src/server/socket-mode.ts : Slack API emulator must implement Socket Mode protocol for bot communication

Applied to files:

  • packages/slack/src/server/web-api.ts
🔇 Additional comments (12)
apps/ui/src/components/Sidebar.svelte (1)

208-220: Looks good — conditional bot avatar rendering is clean.
The fallback to Sparkles keeps the UI consistent when no icon URL is present.

apps/ui/src/components/BotAboutHeader.svelte (1)

13-26: Avatar rendering update looks good.

Conditional rendering is clear and consistent with the intended layout.

apps/ui/src/lib/time.ts (1)

69-92: Looks good: concise helper additions.

Clear, self-contained utilities with consistent formatting and behaviour.

apps/ui/src/components/Message.svelte (4)

43-82: Props and derived state updates look solid.

isGrouped and timestampShort integration are clean and consistent with the new grouping UI.


338-452: DropdownMenu + grouped header rendering looks consistent.

The grouped timestamp/identity logic and menu gating read well.


479-500: ImageBlock integration is clean.

Nice consolidation of image rendering and preview routing.


557-586: Context menu parity is tidy.

The shortcut submenu and delete gating match the main menu behaviour.

apps/ui/src/components/ThreadPanel.svelte (1)

94-100: Grouping logic is clear and minimal.

The 10‑minute window with user matching is straightforward.

apps/ui/src/components/MessagePanel.svelte (1)

266-279: Day separator and grouping flow is coherent.

The isDaySeparator and isGrouped logic reads cleanly and should be easy to maintain.

packages/slack/src/server/web-api.ts (3)

1974-1996: Nice improvement: MIME inference for Block Kit images.

The extension-based inference avoids the hard-coded PNG assumption and keeps the payload more accurate.


1998-2022: Targeted shortcut dispatch looks solid.

The switch to message_action and targeted routing via targetBot aligns with the intended multi-bot behaviour.


1409-1413: No action required. The shortcuts field correctly returns an array.

The code at line 1410 returns shortcuts as an array (bot.appConfig.shortcuts ?? []), which is the intended design. The type definition in apps/ui/src/lib/types.ts (line 583) confirms this: shortcuts: Shortcut[]. The frontend consumer (apps/ui/src/lib/dispatcher.svelte.ts, line 433) expects and handles shortcuts as an array, and the server-side state logic (packages/slack/src/server/state.ts, line 934) also treats it as an array. This is not a breaking change; it's consistent with the established contract.

Likely an incorrect or invalid review comment.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ui/src/components/Message.svelte`:
- Around line 322-327: The shortcut payload sent from handleShortcut should
include the message's Block Kit blocks so actions receive full content; update
the call to triggerMessageShortcut (in function handleShortcut) to add
message.blocks to the payload alongside ts, text, and file (use
shortcut.callback_id as before) so the payload contains { ts: message.ts, text:
message.text, file: message.file, blocks: message.blocks }.

In `@apps/ui/src/components/MessagePanel.svelte`:
- Around line 50-60: Update imports in MessagePanel.svelte to use the $lib alias
instead of relative '../lib' paths: replace imports for clearChannelMessages,
deleteMessage (from dispatcher), getChannelDisplayName, getChannelMessages,
getParentMessages, getReplyCount, hasThreadDraft, simulatorState (from state),
the Channel type (from types), and formatDateLabel, getDateKey, isWithinMinutes
(from time) so they all import from '$lib/...' counterparts; ensure named
imports and type imports remain unchanged and update any import specifiers to
the matching $lib module names.

In `@packages/slack/src/server/web-api.ts`:
- Around line 1960-1973: The current logic that builds the files array drops
other attachments when storedMessage.file exists; update the files construction
(the storedMessage and files variables) to merge storedMessage.file metadata
into the original message.files instead of replacing it: take message.files (or
empty array), map over it and for any file whose url_private matches
storedMessage.file.url_private (or other identifying field) replace/merge its
properties with storedMessage.file so stored metadata is preferred, and if
storedMessage.file exists but isn’t present in message.files append it; ensure
the resulting files array preserves all original attachments while using
storedMessage.file fields where available.

---

Outside diff comments:
In `@apps/ui/src/components/Message.svelte`:
- Around line 9-39: Replace the relative imports that target the app's src/lib
directory with the $lib path alias: update the imports that currently reference
'../lib/types' (symbols: SimulatorMessage, Shortcut, SlackBlock, SlackOption,
SlackSectionBlock, SlackActionsBlock, SlackContextActionsBlock,
SlackInputBlock), '../lib/state.svelte' (functions: getAllMessageShortcuts,
getChannelDisplayName, simulatorState, isBotUserId, getBotByUserId),
'../lib/dispatcher.svelte' (sendMessageBlockAction, triggerMessageShortcut), and
'../lib/time' (formatTimestamp, formatTimestampShort, formatRelativeTime,
formatFullDate) to use '$lib/...' instead; leave local component imports like
'./blockkit/BlockKitRenderer.svelte', './blockkit/blocks/ImageBlock.svelte', and
'./blockkit/context' unchanged. Ensure only the path prefix changes so imports
resolve via the $lib alias.

---

Duplicate comments:
In `@apps/ui/src/components/ThreadPanel.svelte`:
- Around line 3-12: Update the import paths in ThreadPanel.svelte to use the
$lib alias instead of relative ../lib paths: replace imports that reference
dispatcher.svelte (deleteMessage, sendMessage), state.svelte (getReplyCount,
getThreadDraft, getThreadMessages, setThreadDraft, simulatorState) and time
(isWithinMinutes) with their corresponding $lib/... module paths so the module
resolution follows the app guideline for Svelte files.

tyom added 4 commits February 22, 2026 01:09
…in section blocks

Mrkdwn links without protocol (e.g. <google.com|label>) were rendered
as raw text instead of clickable links. Section blocks now use <br> for
line breaks matching real Slack spacing. Added three new Block Kit
template samples with action_ids.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ui/src/components/Message.svelte (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Address Prettier formatting issue.

The CI pipeline reports a code style issue. Run prettier --write on this file to resolve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/Message.svelte` at line 1, Prettier formatting in the
Message.svelte component is failing CI; run the formatter (e.g., prettier
--write) on this file so the <script lang="ts"> block and the rest of the Svelte
markup are reformatted to project style and the code style check passes.
🧹 Nitpick comments (4)
packages/mrkdwn/src/mrkdwn-to-html.ts (1)

37-45: Protocol-less link regex may be overly permissive for the labelled case.

The regex on line 39 ([^@#!][^|]*?) matches any labelled link not starting with @, #, or !, regardless of whether it contains a dot. This could incorrectly convert non-URL text like <foo|bar> into <a href="https://foo">bar</a>.

Consider aligning with the unlabelled regex (line 43) by requiring a dot to validate it looks like a domain:

💡 Suggested fix to require a dot in labelled links
  // Links without protocol: <domain.com|label> and <domain.com>
  text = text.replace(
-    /&lt;([^@#!][^|]*?)\|(.*?)&gt;/g,
+    /&lt;([^@#!][^|]*?\.[^|]*?)\|(.*?)&gt;/g,
    '<a href="https://$1" target="_blank" rel="noopener noreferrer">$2</a>'
  )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mrkdwn/src/mrkdwn-to-html.ts` around lines 37 - 45, The labelled
protocol-less link regex in mrkdwnToHtml (currently using the pattern starting
with [^@#!][^|]*?) is too permissive and will convert tags like <foo|bar> into
links; update the labelled-link replacement to mirror the unlabelled pattern by
requiring a dot in the captured link portion (i.e., require a hostname-like
token) so the replacement in mrkdwnToHtml only creates <a href="https://...">
for domain-like values; locate the two replacements (the function/variable
performing the two text.replace calls) and change the first regex to include the
dot requirement used by the second.
apps/ui/src/components/blockkit/blocks/SectionBlock.svelte (1)

2-12: Use $lib path alias for imports from src/lib/.

The type imports use a relative path ../../../lib/types instead of the $lib alias. As per coding guidelines, imports from src/lib/ in Svelte applications should use the path alias.

♻️ Suggested fix
 import type {
   SlackSectionBlock,
   SlackButtonElement,
   SlackStaticSelectElement,
   SlackOverflowElement,
   SlackRadioButtonsElement,
   SlackCheckboxesElement,
   SlackDatePickerElement,
   SlackTimePickerElement,
   SlackWorkspaceSelectElement,
-} from '../../../lib/types'
+} from '$lib/types'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/blockkit/blocks/SectionBlock.svelte` around lines 2 -
12, The import statement in SectionBlock.svelte uses a relative path
'../../../lib/types' for types like SlackSectionBlock, SlackButtonElement,
SlackStaticSelectElement, etc.; update that import to use the Svelte path alias
by replacing the module source with '$lib/types' so all referenced types
(SlackSectionBlock, SlackButtonElement, SlackStaticSelectElement,
SlackOverflowElement, SlackRadioButtonsElement, SlackCheckboxesElement,
SlackDatePickerElement, SlackTimePickerElement, SlackWorkspaceSelectElement) are
imported from '$lib/types'.
apps/ui/src/components/blockkit/context.ts (1)

3-7: Use $lib path alias for imports from src/lib/.

The import on line 7 uses a relative path instead of the $lib alias. As per coding guidelines, imports from src/lib/ should use the path alias.

♻️ Suggested fix
 import { mrkdwnToHtml, resolveEmoji } from '@botarium/mrkdwn'
 import DOMPurify from 'dompurify'
 import type {
   SlackViewTextObject,
   SlackOption,
   UploadedFile,
-} from '../../lib/types'
+} from '$lib/types'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/blockkit/context.ts` around lines 3 - 7, The import
statement in apps/ui/src/components/blockkit/context.ts uses a relative path to
'../../lib/types'; update it to use the $lib alias instead by replacing that
import with one that imports SlackViewTextObject, SlackOption, and UploadedFile
from '$lib/types' so the module resolution follows the project's path-aliasing
convention.
apps/ui/src/components/Message.svelte (1)

363-394: Consider extracting duplicated shortcut menu rendering.

The shortcut submenu content (bot icon, shortcut name, app name) is duplicated between the DropdownMenu (lines 369–394) and ContextMenu (lines 567–588). Extracting this into a Svelte snippet or shared component would reduce duplication and simplify future maintenance.

💡 Example using Svelte 5 snippet
{`#snippet` shortcutItem(group, shortcut)}
  <span class="flex items-center gap-2 w-full">
    {`#if` group.botIcon?.startsWith('http')}
      <img src={group.botIcon} alt="" class="size-4 rounded object-cover shrink-0" />
    {:else}
      <span class="shrink-0 text-sm">{group.botIcon || group.botName.charAt(0).toUpperCase()}</span>
    {/if}
    <span class="font-semibold">{shortcut.name}</span>
    <span class="text-slack-text-muted ml-auto text-xs">{group.botName}</span>
  </span>
{/snippet}

Then use {@render shortcutItem(group, shortcut)} in both menus.

Also applies to: 563-588

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/Message.svelte` around lines 363 - 394, The shortcut
menu item markup is duplicated between the DropdownMenu and ContextMenu
rendering; extract the repeated span block into a reusable Svelte snippet or
small component and use it in both places to reduce duplication. Locate the
repeated code inside the loops over shortcutGroups/shortcut (references:
shortcutGroups, group, shortcut, DropdownMenu.Item, handleShortcut) and replace
the inner content with a call to the shared snippet/component (e.g., a Svelte 5
{`#snippet` ...} or a <ShortcutItem /> component) that accepts group and shortcut
props/params, keeping onclick={() => handleShortcut(shortcut)} on the menu item
wrappers.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99f0ccd and f06a252.

📒 Files selected for processing (10)
  • apps/showcase-bot/src/messages/blocks/13-template-approval.json
  • apps/showcase-bot/src/messages/blocks/14-template-notification.json
  • apps/showcase-bot/src/messages/blocks/15-template-vote.json
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
  • apps/ui/src/components/blockkit/context.ts
  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
  • packages/mrkdwn/src/mrkdwn-to-html.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable names in TypeScript/JavaScript code
No semicolons in code, enforced by Prettier
Use single quotes instead of double quotes
Use trailing commas in es5 format
Prefix unused variables with underscore (_)

Files:

  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
  • packages/mrkdwn/src/mrkdwn-to-html.ts
  • apps/ui/src/components/blockkit/context.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.ts: Test files should be colocated with source code using *.test.ts naming convention
Use Bun's built-in test runner (bun:test) for all tests

Files:

  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
packages/mrkdwn/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Slack mrkdwn converter must provide bidirectional conversion (mrkdwn to HTML and Markdown to mrkdwn)

Files:

  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
  • packages/mrkdwn/src/mrkdwn-to-html.ts
apps/ui/src/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (CLAUDE.md)

Use $lib path alias when importing from src/lib/ in Svelte applications

Files:

  • apps/ui/src/components/blockkit/context.ts
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
**/*.svelte

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.svelte: Use {@html} in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)
Use Svelte 5 runes ($state, $derived, $effect) for reactivity instead of legacy stores

Files:

  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
🧠 Learnings (6)
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to packages/mrkdwn/src/**/*.ts : Slack mrkdwn converter must provide bidirectional conversion (mrkdwn to HTML and Markdown to mrkdwn)

Applied to files:

  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
  • packages/mrkdwn/src/mrkdwn-to-html.ts
  • apps/ui/src/components/blockkit/context.ts
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to **/*.svelte : Use `{html}` in Svelte for mrkdwn rendering (ESLint rule disabled for this specific use case)

Applied to files:

  • packages/mrkdwn/src/mrkdwn-to-html.test.ts
  • packages/mrkdwn/src/mrkdwn-to-html.ts
  • apps/ui/src/components/blockkit/context.ts
  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
📚 Learning: 2026-01-14T21:36:07.933Z
Learnt from: tyom
Repo: tyom/botarium PR: 1
File: apps/ui/src/components/MessagePanel.svelte:10-23
Timestamp: 2026-01-14T21:36:07.933Z
Learning: In the client-only Botarium UI (Vite + Svelte), do not add or rely on SSR-specific guards (e.g., checks for sessionStorage) in components. Since the app runs only in Electron or the browser, code that accesses browser APIs will execute in a non-SSR environment, so SSR-unsafe checks are unnecessary and can be removed for maintainability.

Applied to files:

  • apps/ui/src/components/blockkit/blocks/SectionBlock.svelte
  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/Message.svelte
  • apps/ui/src/components/MessagePanel.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to apps/ui/src/**/*.{ts,tsx,svelte} : Use `$lib` path alias when importing from `src/lib/` in Svelte applications

Applied to files:

  • apps/ui/src/components/ThreadPanel.svelte
  • apps/ui/src/components/MessagePanel.svelte
📚 Learning: 2026-02-22T01:14:27.310Z
Learnt from: tyom
Repo: tyom/botarium PR: 13
File: packages/slack/src/server/web-api.ts:1960-1973
Timestamp: 2026-02-22T01:14:27.310Z
Learning: The Botarium simulator (packages/slack/src/server) only supports single file uploads, so `storedMessage.file` (singular) is the correct data structure and can replace `message.files` when present.

Applied to files:

  • apps/ui/src/components/Message.svelte
📚 Learning: 2026-02-20T00:31:35.696Z
Learnt from: CR
Repo: tyom/botarium PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T00:31:35.696Z
Learning: Applies to **/*.svelte : Use Svelte 5 runes (`$state`, `$derived`, `$effect`) for reactivity instead of legacy stores

Applied to files:

  • apps/ui/src/components/MessagePanel.svelte
🧬 Code graph analysis (2)
packages/mrkdwn/src/mrkdwn-to-html.test.ts (1)
packages/mrkdwn/src/mrkdwn-to-html.ts (1)
  • mrkdwnToHtml (102-215)
apps/ui/src/components/blockkit/context.ts (1)
packages/mrkdwn/src/mrkdwn-to-html.ts (1)
  • mrkdwnToHtml (102-215)
🪛 GitHub Actions: CI
apps/ui/src/components/Message.svelte

[warning] 1-1: Code style issues found in the above file. Run Prettier with --write to fix.

🔇 Additional comments (13)
packages/mrkdwn/src/mrkdwn-to-html.ts (1)

102-105: Good addition of the useBr option for flexible line-break rendering.

The optional options parameter with useBr provides a clean way to toggle between <br> tags and styled spans without breaking existing callers.

packages/mrkdwn/src/mrkdwn-to-html.test.ts (1)

47-63: Good test coverage for protocol-less link handling.

These tests validate the new link forms well. Consider adding a negative test case for inputs like <foo|bar> (no dot) to document expected behaviour and guard against unintended matches.

apps/ui/src/components/blockkit/context.ts (1)

48-63: Clean integration of the useBr option.

The signature change properly threads the options parameter through to mrkdwnToHtml, allowing callers to control line-break rendering. The plain text branch correctly bypasses mrkdwn parsing.

apps/ui/src/components/blockkit/blocks/SectionBlock.svelte (1)

40-47: Correct use of useBr: true for compact line breaks in section blocks.

Passing { useBr: true } to renderMrkdwn aligns with the PR objective to use actual <br> elements for more compact rendering in section blocks. The {@html} directive is correctly used for mrkdwn rendering.

apps/ui/src/components/Message.svelte (6)

1-41: LGTM!

Imports are correctly structured using the $lib path alias and appropriate type imports.


43-82: LGTM!

Props and derived state correctly use Svelte 5 runes ($props, $derived, $derived.by). The grouping and shortcut-related derived values are well-structured.


322-328: LGTM!

The handleShortcut function correctly passes the essential message identifiers. The server enriches the payload with full message data including blocks.


397-399: LGTM!

The separator is now correctly gated by onDelete, preventing an orphaned divider when only shortcuts are present.

Also applies to: 592-594


414-442: LGTM!

The grouped message rendering correctly displays a compact timestamp on hover when isGrouped is true, and falls back to the full avatar/name display otherwise. The conditional class application for bot icons is well-handled.


481-504: LGTM!

Good use of the ImageBlock component for file attachments, maintaining consistency with Block Kit image rendering. The max-height constraint on the wrapper ensures thumbnails don't dominate the message area.

apps/ui/src/components/ThreadPanel.svelte (2)

1-14: LGTM!

Imports correctly use the $lib path alias and include the necessary utilities for grouping logic.


94-103: LGTM!

The reply grouping logic is well-implemented. Using parentMessage as the reference for the first reply (index 0) ensures continuity from the thread root, whilst subsequent replies correctly compare against the previous reply in the list.

apps/ui/src/components/MessagePanel.svelte (1)

266-278: LGTM!

The day separator and message grouping logic is well-structured:

  • Short-circuit evaluation in isDaySeparator safely handles the first message case before accessing prevMessage.ts
  • The isGrouped computation correctly combines day boundary, user continuity, and the 10-minute window check
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/showcase-bot/src/messages/blocks/13-template-approval.json`:
- Around line 65-77: Fix the user-facing typos inside the JSON block values:
update the "text" value for the "*When:*" entry (currently "Submitted Aut 10")
to "Submitted Aug 10" and remove the stray trailing double-quote from the
"*Specs:*" value (currently "\"Cheetah Pro 15\" - Fast, really fast\"") so it
becomes "\"Cheetah Pro 15\" - Fast, really fast". Locate these strings in the
13-template-approval.json block entries for "When" and "Specs" and update the
text fields accordingly.

In `@apps/showcase-bot/src/messages/blocks/15-template-vote.json`:
- Around line 44-47: Update the user-facing alt text in the image block: replace
the incorrect "Pam Beasely" value for the "alt_text" property in the image
object (the block containing "type": "image" and "image_url":
"https://api.slack.com/img/blocks/bkb_template_images/profile_3.png") with the
correct surname "Pam Beesly".

---

Outside diff comments:
In `@apps/ui/src/components/Message.svelte`:
- Line 1: Prettier formatting in the Message.svelte component is failing CI; run
the formatter (e.g., prettier --write) on this file so the <script lang="ts">
block and the rest of the Svelte markup are reformatted to project style and the
code style check passes.

---

Duplicate comments:
In `@apps/ui/src/components/MessagePanel.svelte`:
- Around line 50-60: No changes required: the imports in MessagePanel.svelte
correctly use the $lib path alias (symbols: clearChannelMessages, deleteMessage,
getChannelDisplayName, getChannelMessages, getParentMessages, getReplyCount,
hasThreadDraft, simulatorState, formatDateLabel, getDateKey, isWithinMinutes),
so leave these import statements as-is and no code edits are necessary.

---

Nitpick comments:
In `@apps/ui/src/components/blockkit/blocks/SectionBlock.svelte`:
- Around line 2-12: The import statement in SectionBlock.svelte uses a relative
path '../../../lib/types' for types like SlackSectionBlock, SlackButtonElement,
SlackStaticSelectElement, etc.; update that import to use the Svelte path alias
by replacing the module source with '$lib/types' so all referenced types
(SlackSectionBlock, SlackButtonElement, SlackStaticSelectElement,
SlackOverflowElement, SlackRadioButtonsElement, SlackCheckboxesElement,
SlackDatePickerElement, SlackTimePickerElement, SlackWorkspaceSelectElement) are
imported from '$lib/types'.

In `@apps/ui/src/components/blockkit/context.ts`:
- Around line 3-7: The import statement in
apps/ui/src/components/blockkit/context.ts uses a relative path to
'../../lib/types'; update it to use the $lib alias instead by replacing that
import with one that imports SlackViewTextObject, SlackOption, and UploadedFile
from '$lib/types' so the module resolution follows the project's path-aliasing
convention.

In `@apps/ui/src/components/Message.svelte`:
- Around line 363-394: The shortcut menu item markup is duplicated between the
DropdownMenu and ContextMenu rendering; extract the repeated span block into a
reusable Svelte snippet or small component and use it in both places to reduce
duplication. Locate the repeated code inside the loops over
shortcutGroups/shortcut (references: shortcutGroups, group, shortcut,
DropdownMenu.Item, handleShortcut) and replace the inner content with a call to
the shared snippet/component (e.g., a Svelte 5 {`#snippet` ...} or a <ShortcutItem
/> component) that accepts group and shortcut props/params, keeping onclick={()
=> handleShortcut(shortcut)} on the menu item wrappers.

In `@packages/mrkdwn/src/mrkdwn-to-html.ts`:
- Around line 37-45: The labelled protocol-less link regex in mrkdwnToHtml
(currently using the pattern starting with [^@#!][^|]*?) is too permissive and
will convert tags like <foo|bar> into links; update the labelled-link
replacement to mirror the unlabelled pattern by requiring a dot in the captured
link portion (i.e., require a hostname-like token) so the replacement in
mrkdwnToHtml only creates <a href="https://..."> for domain-like values; locate
the two replacements (the function/variable performing the two text.replace
calls) and change the first regex to include the dot requirement used by the
second.

…wn regex

Fix typos in showcase bot templates (Aut→Aug, stray quote, Beasely→Beesly),
use $lib alias in SectionBlock import, extract duplicated shortcut menu item
markup into a Svelte snippet, and tighten protocol-less labelled link regex
to require a dot in the URL portion.
@tyom tyom merged commit ae00a94 into main Feb 22, 2026
2 checks passed
@tyom tyom deleted the shortcuts branch February 22, 2026 01:36
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.

1 participant