Skip to content

Fix reaction display and add assistant status indicator#16

Merged
tyom merged 3 commits intomainfrom
dev
Feb 23, 2026
Merged

Fix reaction display and add assistant status indicator#16
tyom merged 3 commits intomainfrom
dev

Conversation

@tyom
Copy link
Owner

@tyom tyom commented Feb 23, 2026

Details

  • Fix race condition where emoji reactions (e.g., thinking indicator) never appeared on messages in the UI
  • Add support for assistant.threads.setStatus API, displaying bot status in the message panel

The reaction bug was caused by handleSimulatorUserMessage awaiting the WebSocket bot dispatch before returning the HTTP response. This allowed bots to call reactions.add before the UI had stored the message, so addReactionToMessage silently failed on lookup. Fixed by returning the response immediately and dispatching to bots asynchronously.

The assistant status feature adds a new assistant_thread_status SSE event type, state management for per-bot status, and a styled status indicator in the message panel that auto-clears when the bot sends a message.

Summary by CodeRabbit

  • New Features
    • Bot assistant status is shown in the message panel with avatar/icon, name and status.
    • Panel auto-scrolls to the bottom when new bot statuses appear.
    • Assistant thread status events are recognised and processed.
    • New assistant thread management endpoints enable setting thread status and suggested prompts.

tyom added 2 commits February 23, 2026 18:30
Return HTTP response from user-message endpoint before dispatching
to bots. Previously, the handler awaited WebSocket ACK, allowing
bots to call reactions.add before the UI stored the message.
Add support for assistant.threads.setStatus and setSuggestedPrompts
API endpoints. Display bot status (e.g., "is thinking...") in the
message panel with proper styling and auto-scroll. Status auto-clears
when the bot sends a message.
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds assistant-status handling end-to-end: new state APIs to set/clear per-bot assistantStatus, SSE handling to receive status events and clear status on bot messages, UI rendering and auto-scroll for bots with status, and server endpoints to set thread status and suggested prompts.

Changes

Cohort / File(s) Summary
Type Definitions
apps/ui/src/lib/types.ts, packages/slack/src/server/types.ts
Added optional assistantStatus?: string to ConnectedBotInfo and added assistant_thread_status variant plus optional status?: string to SimulatorEvent.
State Management
apps/ui/src/lib/state/bots.svelte.ts, apps/ui/src/lib/state.svelte.ts
Exported and implemented setAssistantStatus(botId, status), clearAssistantStatus(botId), and getBotByUserId(userId) to manage per-bot assistantStatus and look up bots by user ID; barrel re-exports added.
Event Dispatcher
apps/ui/src/lib/dispatcher.svelte.ts
Extended SSE event handling: new assistant_thread_status case calls setAssistantStatus/clearAssistantStatus; bot-message handling now clears assistant status for the sending bot.
UI Component
apps/ui/src/components/MessagePanel.svelte
Added derived botsWithStatus, tracked its length in scroll restoration, added effect to auto-scroll when status appears, and renders a bottom list showing bot avatar/icon, name and assistantStatus.
API / Server
packages/slack/src/server/web-api.ts
Added endpoints /api/assistant.threads.setStatus and /api/assistant.threads.setSuggestedPrompts; changed bot dispatch flow to asynchronous dispatchUserMessageToBots() with error handling and logging.

Sequence Diagram

sequenceDiagram
    participant External as External System
    participant API as SlackWebAPI
    participant Dispatcher as SSE Dispatcher
    participant State as Bot State
    participant UI as MessagePanel UI

    External->>API: POST /api/assistant.threads.setStatus
    API->>API: assistantThreadsSetStatus()
    API-->>External: Immediate response

    External->>Dispatcher: SSE event (assistant_thread_status, botId, status)
    Dispatcher->>State: setAssistantStatus(botId, status)
    State->>State: Update simulatorState.connectedBots[botId].assistantStatus

    State-->>UI: Reactive state update
    UI->>UI: derive botsWithStatus
    UI->>UI: auto-scroll on new status
    UI-->>UI: render bot status list

    Dispatcher->>State: on bot message event -> clearAssistantStatus(botId)
    State-->>UI: Reactive state update (status cleared)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 two main changes: fixing reaction display and adding an assistant status indicator, matching the core objectives and file changes throughout the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 dev

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.

🧹 Nitpick comments (2)
apps/ui/src/components/MessagePanel.svelte (1)

193-198: Auto-scroll effect may trigger more often than intended.

This effect runs whenever botsWithStatus.length > 0, which means it will scroll to bottom on every re-render where any bot has a status, not just when a status first appears. If the status text changes (e.g., from "is thinking..." to "is generating..."), this could cause unexpected scrolling.

Consider tracking the previous length to only scroll when a new status appears:

♻️ Suggested refinement
+  let prevBotsWithStatusCount = 0
+
   // Auto-scroll when assistant status appears
   $effect(() => {
-    if (botsWithStatus.length > 0) {
+    const currentCount = botsWithStatus.length
+    if (currentCount > prevBotsWithStatusCount) {
       tick().then(() => requestAnimationFrame(scrollToBottom))
     }
+    prevBotsWithStatusCount = currentCount
   })
🤖 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 193 - 198, The
$effect that auto-scrolls when botsWithStatus.length > 0 is firing on any
re-render while a bot has status; change the $effect to only act when the count
increases by tracking the previous count (e.g. a local variable like
prevBotsWithStatusCount) and compare prevBotsWithStatusCount <
botsWithStatus.length before calling tick().then(() =>
requestAnimationFrame(scrollToBottom)); after scrolling update
prevBotsWithStatusCount = botsWithStatus.length so subsequent status text
changes won't retrigger scrollToBottom unnecessarily.
packages/slack/src/server/web-api.ts (1)

1046-1051: Consider using undefined instead of empty string for cleared status.

Line 1049 uses status || '' which sends an empty string when status is falsy. However, the frontend's clearAssistantStatus sets assistantStatus: undefined, and the dispatcher checks if (event.status) which treats empty string as falsy anyway. This works, but using explicit undefined would be more consistent:

♻️ Suggested refinement for consistency
     this.state.emitEvent({
       type: 'assistant_thread_status',
       channel: channel_id,
-      status: status || '',
+      status: status || undefined,
       botId,
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/slack/src/server/web-api.ts` around lines 1046 - 1051, The emitEvent
call currently sets status using status || '' which sends an empty string for
falsy values; change this to send explicit undefined instead (e.g., replace
status || '' with status || undefined or status ? status : undefined) in the
this.state.emitEvent payload for the 'assistant_thread_status' event so cleared
status matches the frontend's assistantStatus: undefined behavior; update the
status property in the emitEvent call accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/ui/src/components/MessagePanel.svelte`:
- Around line 193-198: The $effect that auto-scrolls when botsWithStatus.length
> 0 is firing on any re-render while a bot has status; change the $effect to
only act when the count increases by tracking the previous count (e.g. a local
variable like prevBotsWithStatusCount) and compare prevBotsWithStatusCount <
botsWithStatus.length before calling tick().then(() =>
requestAnimationFrame(scrollToBottom)); after scrolling update
prevBotsWithStatusCount = botsWithStatus.length so subsequent status text
changes won't retrigger scrollToBottom unnecessarily.

In `@packages/slack/src/server/web-api.ts`:
- Around line 1046-1051: The emitEvent call currently sets status using status
|| '' which sends an empty string for falsy values; change this to send explicit
undefined instead (e.g., replace status || '' with status || undefined or status
? status : undefined) in the this.state.emitEvent payload for the
'assistant_thread_status' event so cleared status matches the frontend's
assistantStatus: undefined behavior; update the status property in the emitEvent
call accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b73e95 and 8b996ba.

📒 Files selected for processing (7)
  • apps/ui/src/components/MessagePanel.svelte
  • 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/types.ts
  • packages/slack/src/server/web-api.ts
📜 Review details
🧰 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/slack/src/server/types.ts
  • apps/ui/src/lib/state.svelte.ts
  • apps/ui/src/lib/types.ts
  • apps/ui/src/lib/state/bots.svelte.ts
  • packages/slack/src/server/web-api.ts
  • apps/ui/src/lib/dispatcher.svelte.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/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/types.ts
  • packages/slack/src/server/web-api.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/lib/state.svelte.ts
  • apps/ui/src/lib/types.ts
  • apps/ui/src/lib/state/bots.svelte.ts
  • apps/ui/src/components/MessagePanel.svelte
  • apps/ui/src/lib/dispatcher.svelte.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/components/MessagePanel.svelte
🧠 Learnings (4)
📚 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/types.ts
  • packages/slack/src/server/web-api.ts
  • apps/ui/src/lib/dispatcher.svelte.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/socket-mode.ts : Slack API emulator must implement Socket Mode protocol for bot communication

Applied to files:

  • packages/slack/src/server/types.ts
  • packages/slack/src/server/web-api.ts
📚 Learning: 2026-01-16T23:30:02.205Z
Learnt from: tyom
Repo: tyom/botarium PR: 2
File: packages/create-bot/templates/slack-bot/src/slack/handlers.ts.tmpl:99-110
Timestamp: 2026-01-16T23:30:02.205Z
Learning: In the Slack bot template's handler (`packages/create-bot/templates/slack-bot/src/slack/handlers.ts.tmpl`), the `hasBotParticipatedInThread` function is a fallback path that only runs after bot restarts. The in-memory `threadTracker` handles active threads. The function intentionally limits to 100 messages to avoid multiple API calls and rate limit pressure for the rare edge case of threads with 100+ messages, where the failure mode is minor (bot won't auto-respond until re-mentioned).

Applied to files:

  • packages/slack/src/server/web-api.ts
📚 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
🧬 Code graph analysis (3)
apps/ui/src/lib/state/bots.svelte.ts (1)
apps/ui/src/lib/state.svelte.ts (3)
  • setAssistantStatus (114-114)
  • simulatorState (31-66)
  • clearAssistantStatus (115-115)
packages/slack/src/server/web-api.ts (2)
packages/slack/src/lib/logger.ts (1)
  • webApiLogger (34-34)
packages/slack/src/server/types.ts (1)
  • SimulatorUserMessageResponse (374-378)
apps/ui/src/lib/dispatcher.svelte.ts (1)
apps/ui/src/lib/state/bots.svelte.ts (2)
  • clearAssistantStatus (159-167)
  • setAssistantStatus (148-156)
🔇 Additional comments (15)
packages/slack/src/server/types.ts (1)

396-414: LGTM!

The new assistant_thread_status event type and optional status field are well-integrated with the existing SimulatorEvent type structure. This properly supports SSE event broadcasting for assistant status updates as per the project's architecture.

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

586-586: LGTM!

The optional assistantStatus field is a clean addition to ConnectedBotInfo, properly typed and well-positioned within the interface.

apps/ui/src/lib/state/bots.svelte.ts (2)

143-167: LGTM!

The assistant status state management functions are well-implemented:

  • Immutable update pattern with spread operator ensures proper reactivity with SvelteMap.
  • Silent no-op when bot doesn't exist is appropriate for handling status updates from disconnected bots.
  • The conditional check in clearAssistantStatus avoids unnecessary map updates.

169-206: LGTM!

The bot identity helpers are well-documented and handle both legacy (U_BOT) and new multi-bot formats (U_{botId}) appropriately.

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

114-115: LGTM!

Clean re-exports following the established barrel file pattern.

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

130-134: LGTM!

Clean derived state using Svelte 5 $derived rune to filter bots with active status.


301-333: LGTM!

The status indicator UI is well-structured with proper conditional rendering for bot icons and consistent styling with the APP badge.

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

23-24: LGTM!

Imports added for the new assistant status management functions.


340-346: LGTM!

Good implementation of auto-clearing assistant status when a bot sends a message, mirroring real Slack behaviour. The botId extraction correctly handles the U_{botId} format.


466-475: LGTM!

The assistant_thread_status event handler correctly sets or clears status based on whether event.status is truthy, aligning with the backend implementation.

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

1326-1332: Excellent fix for the race condition.

Returning the HTTP response immediately before dispatching to bots ensures the UI stores the message before any bot reactions arrive via SSE. The void operator correctly handles the floating Promise.


1059-1069: Stub endpoint noted.

The assistantThreadsSetSuggestedPrompts is currently a no-op stub that returns success. If this is intentional for future implementation, consider adding a TODO comment for clarity.


1340-1393: LGTM!

The asynchronous bot dispatch is well-structured with proper error handling that logs failures without blocking the response path. The app_mention logic correctly targets individual bots based on mentions.


505-520: LGTM!

Good addition of warning logs for debugging reactions.add failures - helps diagnose issues when arguments are missing or messages aren't found.


264-274: LGTM!

Clean routing additions for the new assistant API endpoints following the existing pattern.

Track previous bot status count so scrollToBottom only fires when new
statuses appear, not on every re-render. Also send undefined instead of
empty string for cleared assistant status to match frontend convention.
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.

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

1059-1069: Consider adding input validation for consistency.

This endpoint doesn't validate channel_id, unlike assistantThreadsSetStatus above. If this is intentionally a stub (acknowledge-only), consider adding a brief comment to clarify. Otherwise, add validation for consistency:

♻️ Suggested validation
 private assistantThreadsSetSuggestedPrompts(body: {
   channel_id?: string
   thread_ts?: string
   prompts?: unknown[]
   title?: string
 }): Response {
+  // Stub: acknowledge the request without storing prompts
+  if (!body.channel_id) {
+    return Response.json(
+      { ok: false, error: 'missing_argument' },
+      { headers: corsHeaders() }
+    )
+  }
+
   webApiLogger.debug(
     `assistant.threads.setSuggestedPrompts: ${body.title || '(no title)'}, ${body.prompts?.length ?? 0} prompt(s)`
   )
   return Response.json({ ok: true }, { headers: corsHeaders() })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/slack/src/server/web-api.ts` around lines 1059 - 1069, The
assistantThreadsSetSuggestedPrompts handler lacks the same channel_id validation
present in assistantThreadsSetStatus; either add the same input check or
explicitly mark this function as an acknowledge-only stub with a comment. Locate
assistantThreadsSetSuggestedPrompts and implement the same validation strategy
used in assistantThreadsSetStatus: verify body.channel_id (and any other
required fields like thread_ts) and return a JSON error Response with
corsHeaders when missing/invalid, or add a one-line comment above the function
stating it intentionally does no validation and is acknowledge-only so future
reviewers know this is deliberate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/slack/src/server/web-api.ts`:
- Around line 1059-1069: The assistantThreadsSetSuggestedPrompts handler lacks
the same channel_id validation present in assistantThreadsSetStatus; either add
the same input check or explicitly mark this function as an acknowledge-only
stub with a comment. Locate assistantThreadsSetSuggestedPrompts and implement
the same validation strategy used in assistantThreadsSetStatus: verify
body.channel_id (and any other required fields like thread_ts) and return a JSON
error Response with corsHeaders when missing/invalid, or add a one-line comment
above the function stating it intentionally does no validation and is
acknowledge-only so future reviewers know this is deliberate.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b996ba and c841dee.

📒 Files selected for processing (2)
  • apps/ui/src/components/MessagePanel.svelte
  • packages/slack/src/server/web-api.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/components/MessagePanel.svelte
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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/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-16T23:30:02.205Z
Learnt from: tyom
Repo: tyom/botarium PR: 2
File: packages/create-bot/templates/slack-bot/src/slack/handlers.ts.tmpl:99-110
Timestamp: 2026-01-16T23:30:02.205Z
Learning: In the Slack bot template's handler (`packages/create-bot/templates/slack-bot/src/slack/handlers.ts.tmpl`), the `hasBotParticipatedInThread` function is a fallback path that only runs after bot restarts. The in-memory `threadTracker` handles active threads. The function intentionally limits to 100 messages to avoid multiple API calls and rate limit pressure for the rare edge case of threads with 100+ messages, where the failure mode is minor (bot won't auto-respond until re-mentioned).

Applied to files:

  • 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/socket-mode.ts : Slack API emulator must implement Socket Mode protocol for bot communication

Applied to files:

  • 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/web-api.ts
🧬 Code graph analysis (1)
packages/slack/src/server/web-api.ts (2)
packages/slack/src/lib/logger.ts (1)
  • webApiLogger (34-34)
packages/slack/src/server/types.ts (1)
  • SimulatorUserMessageResponse (374-378)
🔇 Additional comments (3)
packages/slack/src/server/web-api.ts (3)

264-274: LGTM!

The new Assistant API routes follow the existing routing pattern and correctly pass the token to setStatus for bot identification.


504-524: LGTM!

Good debugging additions — logging the specific values helps troubleshoot reaction failures related to the race condition fix.


1326-1393: Good fix for the race condition.

Returning the HTTP response immediately before dispatching to bots ensures the UI stores the message before any bot reactions arrive via SSE. The fire-and-forget pattern with internal try/catch is appropriate here.

@tyom tyom merged commit bcb86b6 into main Feb 23, 2026
2 checks passed
@tyom tyom deleted the dev branch February 24, 2026 23:51
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