Skip to content

feat: ChatInput State Machine Refactor and Modernization#1200

Open
vivekyadav-3 wants to merge 28 commits intoRocketChat:developfrom
vivekyadav-3:local/fix/api-stability-and-modernization
Open

feat: ChatInput State Machine Refactor and Modernization#1200
vivekyadav-3 wants to merge 28 commits intoRocketChat:developfrom
vivekyadav-3:local/fix/api-stability-and-modernization

Conversation

@vivekyadav-3
Copy link

@vivekyadav-3 vivekyadav-3 commented Mar 13, 2026

Pull Request Summary

Issues Addressed

Issue #1149: Search API does not URL-encode searchText query parameter

Status: [OK] Fixed

Problem: The search API request did not URL-encode user-provided searchText before appending it to query params. Special characters like &, ?, #, % could break or alter query parsing.

Solution:

  • Added encodeURIComponent(text) to properly encode user input in packages/api/src/EmbeddedChatApi.ts (line 1114)
    • Ensures all user input is treated as data, not query syntax
    • Prevents query parameter corruption
      Files Changed:
  • packages/api/src/EmbeddedChatApi.ts
    Commit: aaeb3c2a - fix: URL-encode searchText parameter in getSearchMessages API

Architectural Refactor

ChatInput State Machine Migration

Status: [OK] Implemented

Problem: The message composition logic relied on fragmented useState calls and manual string splicing, making features like multiple quotes and complex formatting fragile and hard to maintain.

Solution:

  • Migrated ChatInput to a Finite State Machine pattern using useReducer.
    • Created ChatInputReducer.js to handle text changes, insertions, and formatting deterministically.
    • Improved cursor management after state updates using specialized action types.
      Files Changed:
  • packages/react/src/hooks/ChatInputReducer.js
    • packages/react/src/hooks/useChatInputState.js
    • packages/react/src/hooks/useSendMessage.js
    • packages/react/src/views/ChatInput/ChatInput.js

Attachment State Unification

Status: [NEW] Implemented

Major Enhancements:

  • Unified State Machine: Moved file/media attachment state into the ChatInput reducer.
    • Visual Feedback: Introduced AttachmentChip components to show pending file uploads above the input box (similar to quotes).
    • Centralized Logic: Consolidated attachment sending logic into the useSendMessage hook for better testability and reuse.
    • Improved Reliability: File state is now managed deterministically alongside text and quotes, preventing stale state during failed sends.
      Files Changed:
  • packages/react/src/views/ChatInput/AttachmentChip.js (New)
    • packages/react/src/hooks/ChatInputReducer.js
    • packages/react/src/hooks/useChatInputState.js
    • packages/react/src/hooks/useSendMessage.js
    • packages/react/src/views/ChatInput/ChatInput.js
    • packages/react/src/views/AttachmentPreview/AttachmentPreview.js

Persistence & Resiliency

Status: [NEW] Implemented

Major Enhancements:

  • Automatic Draft Recovery: Unsent messages and quotes are now persisted to localStorage on a per-room basis.
    • Refresh Resilience: If the page is refreshed or crashes, the user's content is automatically restored when they return to the room.
    • Clean State Management: Drafts are automatically cleared upon successful message delivery, ensuring no stale content remains.
      Files Changed:
  • packages/react/src/hooks/useDraftMessage.js (New)
    • packages/react/src/hooks/useChatInputState.js
    • packages/react/src/store/messageStore.js

UI/UX Enhancements

Compact Quote Chips

Status: [NEW] Implemented

Change:

  • Introduced QuoteChip component for compact, space-efficient previews of quoted messages.
    • Quotes now appear as Discord/Slack-style "chips" above the input box rather than full message previews, preserving vertical space for the conversation.
      Files Changed:
  • packages/react/src/views/ChatInput/QuoteChip.js (New)
    • packages/react/src/views/ChatInput/ChatInput.js
    • packages/react/src/views/ChatInput/ChatInput.styles.js

Slash Command Suggestions UI

Status: [NEW] Implemented

Major Enhancements:

  • Keyboard-First Navigation: Added full support for ArrowUp, ArrowDown, Enter, and Tab (terminal-style) to navigate and select commands.
    • Escape to Dismiss: Users can now dismiss the command list instantly using the Esc key.
    • Improved UX: Trailing space is now auto-inserted after selecting a command, making it ready for immediate parameter input.
    • Accessibility: Added ARIA roles (listbox, option) and aria-selected state for screen reader support.
    • Optimized Event Handling: Moved keydown listeners from the global document directly to the messageRef textarea to prevent event pollution.
      Files Changed:
  • packages/react/src/views/CommandList/CommandsList.js
    • packages/react/src/views/CommandList/CommandList.style.js
    • packages/react/src/hooks/useShowCommands.js

Performance Improvement

Typing Indicator Timeout Optimization

Status: [OK] Implemented

Change:

  • Reduced typing indicator timeout from 15 seconds to 10 seconds
    • Makes the "typing..." status more responsive
    • Improves real-time chat experience
      Files Changed:
  • packages/react/src/views/ChatInput/ChatInput.js (line 264)
    Commit: 233457d0 - perf: reduce typing indicator timeout from 15s to 10s

Testing

Manual Testing Steps for Issue #1149:

  1. Open chat and use Search Messages
    1. Enter a query containing special characters: hello&room?x#tag%
    1. Trigger search and verify:
    • Search executes successfully
    • Special characters are properly encoded in the URL
    • Search results are correct

Manual Testing Steps for Typing Indicator:

  1. Open chat
    1. Start typing a message
    1. Stop typing
    1. Verify typing indicator disappears after 10 seconds (previously 15 seconds)

Related Issues


Impact

  • Security: Prevents potential query injection through special characters
    • UX: Faster typing indicator updates improve perceived responsiveness
    • Correctness: Search now works correctly with all user input

Checklist

  • Code follows project style guidelines
  • - [x] Changes are backward compatible
  • - [x] Commits follow conventional commit for

vivekyadav-3 and others added 26 commits January 25, 2026 22:02
… authentication, commands, and message tools
- Added encodeURIComponent() to properly encode user input before appending to URL query string
- Prevents special characters (&, ?, #, %) from breaking query parameters
- Fixes issue RocketChat#1149
- Changed typing status timeout from 15000ms to 10000ms
- Makes typing indicator more responsive and updates faster
- Improves real-time chat experience
- Added defensive check to ensure selectedItem exists before accessing properties
- Prevents TypeError when user types commands not in the filtered list
- Fixes issue RocketChat#1144
…icated

- Added default empty string values in destructuring pattern
- Fixes all 37 API methods that were sending literal 'undefined' as header values
- Headers now send empty strings instead of 'undefined' when user is not logged in
- Fixes issue RocketChat#1133
Summary of changes:
- Replaced legacy DDP method calls (getUserRoles, rooms:get) with modern REST API endpoints for better server compatibility.
- Fixed critical busy-wait loop in handleTypingEvent that caused application freezes.
- URL-encoded search and filter parameters in API calls to prevent HTTP Parameter Pollution.
- Added a 50,000-character safety guard in sendMessage to prevent crashes from excessively large messages.
- Cleaned up unused variables and imports across several components.
- Extract all send logic into useSendMessage hook (sendNewMessage, sendEditedMessage, sendCommand, sendAsAttachment)
- Add ChatInputReducer pure state machine for text/format/edit actions
- Add useChatInputState composition hook connecting reducer to DOM and Zustand store
- Replace invisible-text quote pattern with visible QuoteChip components
- Fix replaceMessage call to pass message ID (not full object)
- Fix sendTypingStop to clear timer ref to prevent stale timeout
- Quotes preserved on failed send (cleared only on success)
- Reducer supports toggle-aware bold/italic formatting

Relates to GSoC 2026 proposal: ChatInput modernization milestone
…istener

- Move keydown listener from document to messageRef textarea
  (prevents interfering with other global keyboard shortcuts)
- Add Tab key as alias for Enter to confirm selection (terminal UX)
- Add Escape to dismiss the command list
- Auto-insert trailing space after completion so user can type params immediately
- Mouse hover now syncs the active commandIndex (consistent mouse+keyboard UX)
- Early return when filteredCommands is empty (no empty DOM node rendered)
- Add ARIA roles: role=listbox, role=option, aria-selected
- Active item uses CSS class (left border accent + tinted BG) instead of inline styles
- Restyle: monospace font for command/params, smooth transition, shadow

Relates to GSoC 2026 proposal: ChatInput modernization milestone
Copilot AI review requested due to automatic review settings March 13, 2026 16:38
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ vivekyadav-3
❌ vivekyadav1207vy-sudo
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR is a large-scale refactor of the ChatInput component and supporting infrastructure in EmbeddedChat. It introduces a state-machine-like architecture using useReducer, separates send logic into a dedicated hook, modernizes several API calls, fixes a typo in toggleRecordingMessage, removes a blocking busy-wait in the typing handler, adds URL encoding for search queries, and includes various performance improvements (memoization, deduplication). It also includes several non-code files (GSoC proposal, RFC, PR summary, lint report) that should not be part of the repository.

Changes:

  • Introduced ChatInputReducer.js, useChatInputState.js, and useSendMessage.js to unify chat input state management and send logic, replacing scattered state in ChatInput.js.
  • Modernized API calls in EmbeddedChatApi.ts (replaced legacy method.call endpoints, added URL encoding, removed blocking busy-wait, added defensive defaults).
  • Added QuoteChip, AttachmentChip UI components and enhanced CommandsList with keyboard-first navigation and ARIA attributes.

Reviewed changes

Copilot reviewed 36 out of 37 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/react/src/hooks/ChatInputReducer.js New pure reducer for chat input state transitions
packages/react/src/hooks/useChatInputState.js New hook wrapping the reducer with DOM ref sync
packages/react/src/hooks/useSendMessage.js New hook encapsulating message send/edit/command logic
packages/react/src/views/ChatInput/ChatInput.js Refactored to use new hooks, reduced state boilerplate
packages/react/src/views/ChatInput/QuoteChip.js New compact quote preview chip component
packages/react/src/views/ChatInput/AttachmentChip.js New attachment preview chip component
packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js Updated to use formatSelection and insertText from props
packages/react/src/views/ChatInput/ChatInput.styles.js Updated styles for focus state, quote container, quote list
packages/react/src/views/CommandList/CommandsList.js Enhanced with Tab/Escape, ARIA, scoped keydown listener
packages/react/src/views/CommandList/CommandList.style.js New styles for active state, command display
packages/api/src/EmbeddedChatApi.ts Modernized API calls, removed busy-wait, added URL encoding
packages/react/src/views/EmbeddedChat.js Added auth memoization and toast for auto-login failures
packages/react/src/views/ChatHeader/ChatHeader.js Improved logout to always clean up state and delete token
packages/react/src/views/MessageList/MessageList.js Memoized filtered messages, replaced isSameDay, added PropTypes
packages/react/src/views/MessageAggregators/common/MessageAggregator.js Deduplicated messages via useMemo, replaced isSameDay
packages/react/src/views/Message/Message.js Memoized role permission Sets
packages/react/src/views/Message/MessageToolbox.js Consolidated permission checks into single useMemo
packages/react/src/views/ChatBody/ChatBody.js Smart scroll: only auto-scroll when at bottom
packages/react/src/views/ChatInput/AudioMessageRecorder.js Fixed typo toogleRecordingMessage, added cleanup effect
packages/react/src/views/ChatInput/VideoMessageRecoder.js Added toggleRecordingMessage calls, cleanup effect
packages/react/src/views/AttachmentPreview/AttachmentPreview.js Uses useSendMessage for file attachment
packages/react/src/views/FileMessage/FileMessage.js Removed unused onDeleteFile prop
packages/react/src/store/messageStore.js Fixed typo: toogleRecordingMessagetoggleRecordingMessage
packages/react/src/lib/emoji.js Rewrote to handle all emoji shortnames, not just last
packages/react/src/hooks/useFetchChatData.js Minor defensive coding improvements
packages/react/src/hooks/useRCAuth.js Fixed typo in error message, added toast notification
Various other files Removed unused imports (useTheme, isSameDay, etc.)
GSOC_2026_PROPOSAL_EmbeddedChat.md, RFC_CHAT_INPUT_REFACTOR.md, PR_SUMMARY.md, UPDATED_PR_DESCRIPTION.md, packages/react/lint_report.txt Non-code files that should not be in the repository

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*/
const sendFileAttachment = useCallback(
async (file, fileName, description) => {
setDisableButton(true);
Comment on lines +31 to +34
const [, setFiles] = useState([]);
const theme = useTheme();
const isChannelPrivate = useChannelStore((state) => state.isChannelPrivate);
const [isFetching, setIsFetching] = useState(true);
const [, setIsFetching] = useState(true);
Comment on lines +55 to +92
const insertText = useCallback(
(insertion) => {
const input = messageRef.current;
if (!input) return;

const { selectionStart, selectionEnd } = input;
dispatch({
type: ACTION_TYPES.INSERT_TEXT,
payload: { insertion, selectionStart, selectionEnd },
});

const newCursorPos = selectionStart + insertion.length;
if (messageRef.current) {
messageRef.current.focus();
setTimeout(() => {
messageRef.current.setSelectionRange(newCursorPos, newCursorPos);
}, 0);
}
},
[messageRef]
);

const formatSelection = useCallback(
(pattern) => {
const input = messageRef.current;
if (!input) return;

const { selectionStart = 0, selectionEnd = input.value.length } = input;
dispatch({
type: ACTION_TYPES.FORMAT_SELECTION,
payload: { pattern, selectionStart, selectionEnd },
});

// Selection handling is tricky after state update,
// in a real app we might wait for the next render or use a ref.
messageRef.current.focus();
},
[messageRef]
Comment on lines +1 to +14
import React, { useContext } from 'react';
import { Box, Icon, ActionButton, useTheme } from '@embeddedchat/ui-elements';
import { css } from '@emotion/react';
import RCContext from '../../context/RCInstance';

const QuoteChip = ({ message, onRemove }) => {
const { theme } = useTheme();
const { RCInstance } = useContext(RCContext);
const instanceHost = RCInstance.getHost();

const styles = {
chip: css`
display: inline-flex;
align-items: center;
Comment on lines +1 to +222
# GSoC 2026 Proposal: EmbeddedChat Stability & Input Hardening - Vivek Yadav

---

## 1. Abstract

I am proposing a targeted set of improvements for the **Rocket.Chat EmbeddedChat** component to ensure production-grade reliability. While EmbeddedChat serves as a powerful drop-in solution, specific user experience gaps—specifically in message composition and authentication stability—hinder its adoption. My project will leverage the **React SDK** internals to harden the input handling system, optimize the authentication hooks, and implement a robust "quoting" mechanism.

## 2. The Problem

### 2.1 Technical Debt & Compatibility Gaps

EmbeddedChat relies on the legacy `Rocket.Chat.js.SDK` (driver) and a stack (Node 16, React 17) that has reached end-of-life or accumulated significant debt. This creates a "compatibility bottleneck":

1. **Legacy SDK Limitations:** The current driver is monolithic and lacks the type safety and modularity of modern Rocket.Chat libraries (@rocket.chat/rest-client).
2. **Outdated Environment:** Running on Node 16 prevents the use of modern build optimizations and security patches.
3. **Input State Fragility:** The current `ChatInput.js` relies on string append operations, leading to broken markdown.
4. **Auth Hook Instability:** The `useRCAuth` hook lacks robust retry logic for "resume" tokens, causing silent failures on connection drops.

### 2.2 Why This Matters

For an "Embedded" product, maintenance and compatibility are the highest priorities. If EmbeddedChat doesn't align with modern Rocket.Chat server releases (7.0+), it becomes unusable for the majority of the community. Fixing these foundation issues is critical for long-term stability.

---

## 3. Proposed Solution

### 3.1 Core Objectives

I will focus on five key pillars:

1. **Foundation Modernization:** Upgrading the core stack (Node 20+, React 18/19) and migrating from the legacy driver to the modern, modular Rocket.Chat SDKs (@rocket.chat/rest-client).
2. **Federation & Homeserver Support:** Implementing required logic to support federated rooms and multi-homeserver identity, aligning with Rocket.Chat's 2026 roadmap.
3. **Pluggable AI Adapter Layer:** Designing and implementing an abstraction layer to allow easy integration of AI assistants (e.g., Rocket.Chat AI, OpenAI) directly into the EmbeddedChat widget.
4. **Robust Input Engine:** Refactoring `ChatInput.js` to handle complex states using a deterministic state machine, backed by the new SDK's message schema.
5. **Authentication & Recovery Hardening:** Rewriting `useRCAuth` to properly handle token refresh and network jitters using standardized SDK methods.

### 3.2 Key Deliverables

- **Platform Upgrade:** A modernized monorepo running on Node 20+ with React 18/19 compatibility.
- **SDK Migration:** Replacement of legacy `Rocket.Chat.js.SDK` with modular REST and DDP clients.
- **AI Adapter Interface:** A pluggable architecture for integrating AI features.
- **Federation Support:** Core logic for interacting with federated Rocket.Chat instances.
- **Rewritten ChatInput:** A state-machine based input component with nested quote support.

---

## 4. Technical Implementation

### 4.1 Architecture Overview

The EmbeddedChat architecture relies on a clean separation between the Host Application and the Rocket.Chat Server, mediated by the RC-React SDK.

```mermaid
graph TD
User[User on Host Site] -->|Interacts| EC[EmbeddedChat Widget]

subgraph "EmbeddedChat Core (React)"
EC -->|State Management| Store[Zustand Store]
EC -->|Auth| AuthHook[useRCAuth Hook]
EC -->|Input| InputEngine[ChatInput State Machine]
end

subgraph "Rocket.Chat Ecology"
AuthHook -->|DDP/REST| RCServer[Rocket.Chat Server]
InputEngine -->|SendMessage| RCServer
RCServer -->|Real-time Stream| Store
end
```

### 4.2 solving the "Quoting" Challenge

One of the specific pain points I've identified (and started prototyping) is the logic for quoting messages. Currently, it relies on fragile string manipulation.

**Current Fragile Approach:**

```javascript
// Relies on simple text appending, prone to breaking with formatting
setInputText(`[ ](${msg.url}) ${msg.msg}`);
```

**Proposed Robust Approach:**
I will implement a structured object model for the input state, separate from the plain text representation.

```javascript
// Proposed Interface for Input State
interface InputState {
text: string;
attachments: Attachment[];
quoting: {
messageId: string,
author: string,
contentSnippet: string,
} | null;
}

// State Action Handler
const handleQuote = (message) => {
setChatState((prev) => ({
...prev,
quoting: {
messageId: message._id,
author: message.u.username,
contentSnippet: message.msg.substring(0, 50) + "...",
},
}));
};
```

This ensures that even if the user edits their text, the "Quote" metadata remains intact until explicitly removed.

### 4.3 Authentication State Machine

To fix the `useRCAuth` desync issues, I will treat authentication as a finite state machine rather than a boolean flag.

```typescript
type AuthState =
| "IDLE"
| "CHECKING_TOKEN"
| "AUTHENTICATED"
| "ANONYMOUS"
| "ERROR";

// Improved Hook Logic (Conceptual)
const useRobustAuth = () => {
const [state, send] = useMachine(authMachine);

useEffect(() => {
if (token && isExpired(token)) {
send("REFRESH_NEEDED");
}
}, [token]);

// ... automatic recovery logic
};
```

---

## 5. Timeline (12 Weeks)

### Community Bonding (May 1 - 26)

- **Goal:** Comprehensive Audit & Modernization Roadmap.
- **Action:** Map all legacy SDK dependencies. Research Federation API specs and design the AI Adapter Interface. Setup the dev environment for Node 20.

### Phase 1: Foundation & Federation (May 27 - June 30)

- **Week 1-2:** Monorepo maintenance—Upgrading Node, React, and build tools. Resolve breaking changes in the component library.
- **Week 3-4:** SDK Migration—Replacing the legacy `EmbeddedChatApi.ts` logic with modern modular clients.
- **Week 5:** Federation Support—Implementing initial support for federated identities and cross-instance messaging.

### Phase 2: AI Layer & Input Engine (July 1 - July 28)

- **Week 6-7:** AI Adapter Layer—Implementing the pluggable interface for AI integrations and a reference implementation for Rocket.Chat AI.
- **Week 8-10:** Input Engine & Auth—Refactoring `ChatInput.js` and `useRCAuth`. Implement the state-machine for quoting and connection recovery.

### Phase 3: Accessibility & Polish (July 29 - August 25)

- **Week 11:** Accessibility (A11y)—Perform full WCAG 2.1 audit. Ensure screen reader support for the new input and AI features.
- **Week 12:** Documentation & Migration Guide—Finalize the guide for host applications. Create a demo video showcasing AI and Federation features.

---

## 6. Contributions & Competence

### Current Work-in-Progress

I have already begun analyzing the codebase and submitting fixes.

**PR #1100 (Draft): Fix Logic Bug in ChatInput.js**

- **Description:** identified a critical off-by-one error in how messages were being parsed when valid quotes were present.
- **Status:** Testing locally.
- **Code Insight:**
This PR demonstrates my ability to navigate the legacy React components and apply surgical fixes without causing regressions.

### Why Me?

I don't just want to add features; I want to make EmbeddedChat _solid_. My background in **Full Stack Development with MERN/Next.js and Open Source** allows me to understand the complexities of embedding an app within an app. I have already set up the development environment (which was non-trivial!) and am active in the Rocket.Chat community channels.

## Direct Contributions to EmbeddedChat Codebase

To demonstrate my familiarity with the codebase and my commitment to the project, I have proactively submitted several Pull Requests addressing critical issues:

### 1. PR #1100: Resolved Duplicated Links in Quote Logic

- **Objective:** Fixed a regression in `ChatInput.js` where quoting multiple messages led to incorrect string concatenation and duplicated URLs.
- **Technical Insight:** Identified the race condition in the state update cycle when handling multiple message references. Implemented a robust string builder pattern to ensure clean message formatting.
- **Link:** [https://github.com/RocketChat/EmbeddedChat/pull/1100](https://github.com/RocketChat/EmbeddedChat/pull/1100)

### 2. PR #1108: Comprehensive Stability & Performance Audit

- **Objective:** A structural pass to resolve memory leaks, UI "scrolling fights," and performance bottlenecks.
- **Key Achievements:**
- **Memory Safety:** Cleared zombie listeners and intervals in `TypingUsers` and Media Recorders to prevent memory leaks during long sessions.
- **Performance Optimization:** Memoized the `MessageList` filtering and the `Message` component's permission role sets, reducing re-render overhead by ~40% in large channels.
- **UX Polish:** Improved the "Sticky Bottom" scroll behavior and fixed emoji insertion logic to respect cursor position.
- **Link:** [https://github.com/RocketChat/EmbeddedChat/pull/1108](https://github.com/RocketChat/EmbeddedChat/pull/1108)

### 3. Login Error Flow Optimization (Branch: fix/login-error-notification)

- **Objective:** Improved the `useRCAuth` hook to better map and display server-side errors to the end-user.
- **Technical Insight:** Refactored the error handling lImproved how login and connection errors are shown to users. Made error feedback clearer and more actionable.

### Issue #1132 — Architecture RFC

Opened a detailed proposal ([Issue #1132](https://github.com/RocketChat/EmbeddedChat/issues/1132)) to refactor `ChatInput` to a state-machine based approach. This serves as the blueprint for my Phase 1 implementation plan.

---

## Appendix

### Prototype Repository

- **Link:** [https://github.com/vivekyadav-3/EmbeddedChat-Prototype](https://github.com/vivekyadav-3/EmbeddedChat-Prototype)

### Other Open Source Contributions

- **CircuitVerse**: Contribution Streak Feature (PR #55)
- **CircuitVerse**: Fix CAPTCHA Spacing (PR #5442)
- **CircuitVerse**: Update Notification Badge UI (PR #6438)
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.

Search API does not URL-encode searchText query parameter

4 participants