diff --git a/add_backspace_pill_removal.py b/add_backspace_pill_removal.py
new file mode 100644
index 0000000000..b5580b7e9e
--- /dev/null
+++ b/add_backspace_pill_removal.py
@@ -0,0 +1,62 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Adding backspace removal for both action and mention pills...")
+
+# Find the current backspace handling and enhance it
+old_backspace_handling = ''' // Handle backspace on action pills
+ if (e.key === 'Backspace') {
+ const cursorPos = e.currentTarget.selectionStart;
+ const beforeCursor = value.slice(0, cursorPos);
+
+ // Check if cursor is right after an action pill
+ const actionMatch = beforeCursor.match(/\[([^\]]+)\]$/);
+ if (actionMatch) {
+ e.preventDefault();
+ handleRemoveAction(actionMatch[1]);
+ return;
+ }
+ }'''
+
+new_backspace_handling = ''' // Handle backspace on action and mention pills
+ if (e.key === 'Backspace') {
+ const cursorPos = e.currentTarget.selectionStart;
+ const beforeCursor = value.slice(0, cursorPos);
+
+ console.log('๐ Backspace pressed, cursor at:', cursorPos);
+ console.log('๐ Text before cursor:', beforeCursor);
+
+ // Check if cursor is right after an action pill [Action]
+ const actionMatch = beforeCursor.match(/\[([^\]]+)\]$/);
+ if (actionMatch) {
+ console.log('๐ Found action pill to remove:', actionMatch[1]);
+ e.preventDefault();
+ handleRemoveAction(actionMatch[1]);
+ return;
+ }
+
+ // Check if cursor is right after a mention pill @filename
+ const mentionMatch = beforeCursor.match(/@([^\s]+)$/);
+ if (mentionMatch) {
+ console.log('๐ Found mention pill to remove:', mentionMatch[1]);
+ e.preventDefault();
+ handleRemoveMention(mentionMatch[1]);
+ return;
+ }
+ }'''
+
+content = content.replace(old_backspace_handling, new_backspace_handling)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Added backspace removal for pills:")
+print(" - Enhanced existing action pill removal")
+print(" - Added mention pill removal (@filename)")
+print(" - Added debugging logs for backspace events")
+print(" - Now supports removing both types of pills with backspace")
+print(" - Pills will be removed as complete units")
diff --git a/add_cursor_blink_animation.py b/add_cursor_blink_animation.py
new file mode 100644
index 0000000000..f4df823a2e
--- /dev/null
+++ b/add_cursor_blink_animation.py
@@ -0,0 +1,48 @@
+import re
+
+# Read the main.css file
+with open('ui/desktop/src/styles/main.css', 'r') as f:
+ content = f.read()
+
+print("Adding cursor blink animation to main.css...")
+
+# Find the animations section and add the blink animation
+animations_section = '''/* animations */
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}'''
+
+new_animations_section = '''/* animations */
+@keyframes appear {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes blink {
+ 0%, 50% {
+ opacity: 1;
+ }
+ 51%, 100% {
+ opacity: 0;
+ }
+}'''
+
+content = content.replace(animations_section, new_animations_section)
+
+# Write the modified content back
+with open('ui/desktop/src/styles/main.css', 'w') as f:
+ f.write(content)
+
+print("โ Added cursor blink animation to main.css")
+print(" - @keyframes blink: 0-50% visible, 51-100% hidden")
+print(" - Creates a regular text cursor blinking effect")
+print(" - Can be used with 'animate-blink' class")
diff --git a/add_mention_debugging.py b/add_mention_debugging.py
new file mode 100644
index 0000000000..dc33f14b8a
--- /dev/null
+++ b/add_mention_debugging.py
@@ -0,0 +1,84 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Adding comprehensive debugging for mention detection...")
+
+# Find the mention regex section and add more debugging
+old_mention_detection = ''' // Find all mention matches
+ let mentionMatch;
+ mentionRegex.lastIndex = 0; // Reset regex
+ while ((mentionMatch = mentionRegex.exec(value)) !== null) {
+ allMatches.push({
+ type: 'mention',
+ match: mentionMatch,
+ index: mentionMatch.index,
+ length: mentionMatch[0].length,
+ content: mentionMatch[1] // filename without @
+ });
+ }'''
+
+new_mention_detection = ''' // Find all mention matches
+ let mentionMatch;
+ mentionRegex.lastIndex = 0; // Reset regex
+ console.log('๐ Searching for mentions in value:', value);
+ console.log('๐ Using mention regex:', mentionRegex);
+ while ((mentionMatch = mentionRegex.exec(value)) !== null) {
+ console.log('๐ Found mention match:', mentionMatch);
+ allMatches.push({
+ type: 'mention',
+ match: mentionMatch,
+ index: mentionMatch.index,
+ length: mentionMatch[0].length,
+ content: mentionMatch[1] // filename without @
+ });
+ }'''
+
+content = content.replace(old_mention_detection, new_mention_detection)
+
+# Also add debugging after sorting matches
+old_sort_section = ''' // Sort matches by position
+ allMatches.sort((a, b) => a.index - b.index);
+
+ console.log('๐ Found matches:', allMatches);'''
+
+new_sort_section = ''' // Sort matches by position
+ allMatches.sort((a, b) => a.index - b.index);
+
+ console.log('๐ All matches found:', allMatches);
+ console.log('๐ Match breakdown:', {
+ actions: allMatches.filter(m => m.type === 'action').length,
+ mentions: allMatches.filter(m => m.type === 'mention').length,
+ total: allMatches.length
+ });'''
+
+content = content.replace(old_sort_section, new_sort_section)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Added comprehensive debugging:")
+print(" - Debug logs for mention regex search")
+print(" - Debug logs for found mention matches")
+print(" - Match breakdown statistics")
+print(" - Should help identify if mentions are being detected")
+
+# Also let's test the regex pattern directly
+import re
+
+test_strings = [
+ "@filename.txt",
+ "Hello @test.py world",
+ "@folder/file.js",
+ "Multiple @file1.txt and @file2.py mentions"
+]
+
+mention_regex = re.compile(r'@([^\s]+)')
+
+print("\n๐งช Testing mention regex pattern:")
+for test in test_strings:
+ matches = mention_regex.findall(test)
+ print(f" '{test}' โ {matches}")
diff --git a/add_tailwind_blink_class.py b/add_tailwind_blink_class.py
new file mode 100644
index 0000000000..2b6c095d70
--- /dev/null
+++ b/add_tailwind_blink_class.py
@@ -0,0 +1,27 @@
+import re
+
+# Read the main.css file
+with open('ui/desktop/src/styles/main.css', 'r') as f:
+ content = f.read()
+
+print("Adding Tailwind animate-blink utility class...")
+
+# Find the @theme inline section and add the animation
+theme_inline_section = '''@theme inline {
+ --ease-g2: cubic-bezier(0.55, 0, 1, 0.45);'''
+
+new_theme_inline_section = '''@theme inline {
+ --ease-g2: cubic-bezier(0.55, 0, 1, 0.45);
+
+ --animate-blink: blink 1s step-end infinite;'''
+
+content = content.replace(theme_inline_section, new_theme_inline_section)
+
+# Write the modified content back
+with open('ui/desktop/src/styles/main.css', 'w') as f:
+ f.write(content)
+
+print("โ Added animate-blink utility class to Tailwind theme")
+print(" - Uses the blink keyframe with 1s duration")
+print(" - step-end timing for sharp on/off transitions")
+print(" - infinite loop for continuous blinking")
diff --git a/add_visible_text_selection.py b/add_visible_text_selection.py
new file mode 100644
index 0000000000..446150357b
--- /dev/null
+++ b/add_visible_text_selection.py
@@ -0,0 +1,105 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Adding visible blue text selection highlight...")
+
+# Add selection styling to the hidden textarea
+old_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ }}'''
+
+new_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ }}
+ className="absolute inset-0 w-full h-full resize-none selection:bg-blue-500 selection:text-white"'''
+
+content = content.replace(old_hidden_textarea_style, new_hidden_textarea_style)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+# Also add CSS to the main.css file for better selection styling
+with open('ui/desktop/src/styles/main.css', 'r') as f:
+ css_content = f.read()
+
+# Add selection styling at the end of the file
+selection_css = '''
+/* Text selection styling for RichChatInput */
+.rich-text-input textarea::selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+.rich-text-input textarea::-moz-selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+/* Global selection styling for better visibility */
+::selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+::-moz-selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+'''
+
+if '/* Text selection styling for RichChatInput */' not in css_content:
+ css_content += selection_css
+
+ with open('ui/desktop/src/styles/main.css', 'w') as f:
+ f.write(css_content)
+
+ print("โ Added selection CSS to main.css")
+
+# Also add a wrapper class to the RichChatInput component
+old_wrapper = ''' return (
+
'''
+
+new_wrapper = ''' return (
+
'''
+
+content = content.replace(old_wrapper, new_wrapper)
+
+# Write the final content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Added visible text selection highlighting:")
+print(" - Added selection:bg-blue-500 selection:text-white to textarea")
+print(" - Added CSS rules for ::selection styling")
+print(" - Added rich-text-input wrapper class")
+print(" - Selection should now be clearly visible with blue highlight")
diff --git a/complete_mention_pill_rendering.py b/complete_mention_pill_rendering.py
new file mode 100644
index 0000000000..310d0df1f2
--- /dev/null
+++ b/complete_mention_pill_rendering.py
@@ -0,0 +1,181 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Completing mention pill rendering in RichChatInput...")
+
+# Find the section after we set up the matches and complete the processing logic
+# We need to replace the old action-only processing with the new combined processing
+
+# First, let's find where the old processing logic ends and replace it
+# Look for the section that processes beforeAction
+old_processing_section = ''' console.log('โ Found action match:', match);
+ const beforeAction = value.slice(lastIndex, match.index);
+ const actionLabel = match[1];
+ console.log('๐ Processing action:', { beforeAction, actionLabel, matchIndex: match.index });
+
+ // Add text before action with potential cursor
+ if (beforeAction) {
+ // Split text by cursor position to insert cursor at the right spot
+ let textWithCursor = [];
+ for (let i = 0; i < beforeAction.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(beforeAction[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }
+
+ // Find matching action
+ const actionEntry = Object.entries(ACTION_MAP).find(
+ ([_, config]) => config.label === actionLabel
+ );
+
+ if (actionEntry) {
+ const [actionId, config] = actionEntry;
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );
+ } else {
+ // If no matching action, render as text
+ parts.push(
+
+ {match[0]}
+
+ );
+ }
+
+ currentPos += match[0].length;
+ lastIndex = match.index + match[0].length;
+ }'''
+
+new_processing_section = ''' console.log('โ Found match:', matchData);
+
+ // Add text before this match with potential cursor
+ const beforeMatch = value.slice(lastIndex, index);
+ if (beforeMatch) {
+ let textWithCursor = [];
+ for (let i = 0; i < beforeMatch.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(beforeMatch[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }
+
+ // Add cursor before match if needed
+ if (isFocused && cursorPosition === currentPos) {
+ parts.push(
+
+ );
+ }
+
+ if (type === 'action') {
+ // Handle action pills
+ const actionLabel = content;
+ const actionEntry = Object.entries(ACTION_MAP).find(
+ ([_, config]) => config.label === actionLabel
+ );
+
+ if (actionEntry) {
+ const [actionId, config] = actionEntry;
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );
+ } else {
+ // If no matching action, render as text
+ parts.push(
+
+ {match[0]}
+
+ );
+ }
+ } else if (type === 'mention') {
+ // Handle mention pills
+ const fileName = content; // filename without @
+ const filePath = `@${fileName}`; // full mention text
+
+ parts.push(
+ handleRemoveMention(fileName)}
+ />
+ );
+ }
+
+ currentPos += length;
+ lastIndex = index + length;
+ }'''
+
+content = content.replace(old_processing_section, new_processing_section)
+
+# Add the handleRemoveMention function
+old_handle_remove = ''' const handleRemoveAction = useCallback((actionLabel: string) => {
+ const actionText = `[${actionLabel}]`;
+ const newValue = value.replace(actionText, '');
+ onChange(newValue);
+ }, [value, onChange]);'''
+
+new_handle_remove = ''' const handleRemoveAction = useCallback((actionLabel: string) => {
+ const actionText = `[${actionLabel}]`;
+ const newValue = value.replace(actionText, '');
+ onChange(newValue);
+ }, [value, onChange]);
+
+ const handleRemoveMention = useCallback((fileName: string) => {
+ const mentionText = `@${fileName}`;
+ const newValue = value.replace(mentionText, '');
+ onChange(newValue);
+ }, [value, onChange]);'''
+
+content = content.replace(old_handle_remove, new_handle_remove)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Completed mention pill rendering:")
+print(" - Updated processing logic to handle both actions and mentions")
+print(" - Added MentionPill rendering for @filename patterns")
+print(" - Added handleRemoveMention function for pill removal")
+print(" - Mentions will now appear as green pills with file icon")
diff --git a/comprehensive_action_fix.py b/comprehensive_action_fix.py
new file mode 100644
index 0000000000..88b84ec9a5
--- /dev/null
+++ b/comprehensive_action_fix.py
@@ -0,0 +1,98 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Creating comprehensive fix for action pill population...")
+
+# 1. Clean up the RichChatInput onChange handler
+# Find the current messy onChange and replace with clean version
+old_onchange_pattern = r'onChange=\{\(newValue, cursorPos\) => \{[^}]*\}\}'
+new_onchange = '''onChange={(newValue, cursorPos) => {
+ setDisplayValue(newValue);
+ updateValue(newValue);
+ debouncedSaveDraft(newValue);
+ setHasUserTyped(true);
+
+ // Check for @ mention and / action triggers
+ if (cursorPos !== undefined) {
+ const syntheticTarget = {
+ getBoundingClientRect: () => textAreaRef.current?.getBoundingClientRect?.() || new DOMRect(),
+ selectionStart: cursorPos,
+ selectionEnd: cursorPos,
+ value: newValue,
+ };
+ checkForMention(newValue, cursorPos, syntheticTarget as HTMLTextAreaElement);
+ }
+ }}'''
+
+# Use a more targeted replacement
+lines = content.split('\n')
+start_idx = -1
+end_idx = -1
+brace_count = 0
+in_onchange = False
+
+for i, line in enumerate(lines):
+ if 'onChange={(newValue, cursorPos) => {' in line:
+ start_idx = i
+ in_onchange = True
+ brace_count = line.count('{') - line.count('}')
+ elif in_onchange:
+ brace_count += line.count('{') - line.count('}')
+ if '}}' in line and brace_count <= 0:
+ end_idx = i
+ break
+
+if start_idx != -1 and end_idx != -1:
+ # Replace the onChange handler
+ new_lines = lines[:start_idx] + [' ' + new_onchange] + lines[end_idx + 1:]
+ content = '\n'.join(new_lines)
+ print(f"Fixed onChange handler (lines {start_idx}-{end_idx})")
+
+# 2. Ensure the actionPopover state has cursorPosition
+if 'cursorPosition?: number;' not in content:
+ content = content.replace(
+ 'selectedIndex: number;\n }>({',
+ 'selectedIndex: number;\n cursorPosition?: number;\n }>({'
+ )
+ content = content.replace(
+ 'selectedIndex: 0,\n });',
+ 'selectedIndex: 0,\n cursorPosition: 0,\n });'
+ )
+ print("Added cursorPosition to actionPopover state")
+
+# 3. Make sure checkForMention sets cursorPosition when opening action popover
+content = content.replace(
+ ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: textAreaRect.left,
+ y: textAreaRect.top,
+ },
+ selectedIndex: 0,
+ });''',
+ ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: textAreaRect.left,
+ y: textAreaRect.top,
+ },
+ selectedIndex: 0,
+ cursorPosition: cursorPosition,
+ });'''
+)
+
+print("Updated checkForMention to set cursorPosition")
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Comprehensive fix applied successfully!")
+print("\nKey changes made:")
+print("1. Cleaned up RichChatInput onChange handler")
+print("2. Ensured actionPopover state includes cursorPosition")
+print("3. Updated checkForMention to pass cursorPosition to actionPopover")
+print("4. handleActionSelect should now work correctly with proper cursor position")
diff --git a/create_mention_pill_component.py b/create_mention_pill_component.py
new file mode 100644
index 0000000000..e076fdf9e0
--- /dev/null
+++ b/create_mention_pill_component.py
@@ -0,0 +1,67 @@
+# Create MentionPill component similar to ActionPill
+mention_pill_content = '''import React from 'react';
+import { X, File } from 'lucide-react';
+
+interface MentionPillProps {
+ fileName: string;
+ filePath: string;
+ onRemove?: () => void; // Optional for read-only pills in messages
+ variant?: 'default' | 'message'; // Different styles for input vs message display
+ size?: 'sm' | 'md';
+}
+
+export const MentionPill: React.FC = ({
+ fileName,
+ filePath,
+ onRemove,
+ variant = 'default',
+ size = 'sm'
+}) => {
+ const baseClasses = "inline-flex items-center gap-1.5 font-medium border rounded-full";
+
+ const variantClasses = {
+ default: "bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-700",
+ message: "bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-700"
+ };
+
+ const sizeClasses = {
+ sm: "px-2 py-1 text-xs",
+ md: "px-3 py-1.5 text-sm"
+ };
+
+ return (
+
+
+
+ {fileName}
+
+ {onRemove && (
+
+ )}
+
+ );
+};
+
+export default MentionPill;
+'''
+
+# Write the MentionPill component
+with open('ui/desktop/src/components/MentionPill.tsx', 'w') as f:
+ f.write(mention_pill_content)
+
+print("โ Created MentionPill component:")
+print(" - Similar structure to ActionPill")
+print(" - Uses green colors to differentiate from actions (blue)")
+print(" - Shows File icon and filename")
+print(" - Tooltip shows full file path")
+print(" - Supports removal and different variants")
diff --git a/debug_action_flow.py b/debug_action_flow.py
new file mode 100644
index 0000000000..df330d8a4e
--- /dev/null
+++ b/debug_action_flow.py
@@ -0,0 +1,75 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Add debugging to handleActionSelect function
+old_function_pattern = r' const handleActionSelect = \(actionId: string\) => \{[^}]*console\.log\([^)]*\);\s*setActionPopover[^}]*\};'
+
+new_function = ''' const handleActionSelect = (actionId: string) => {
+ console.log('๐ฏ handleActionSelect called with:', actionId);
+ const actionInfo = getActionInfo(actionId);
+ console.log('๐ Action info:', actionInfo);
+
+ // Get current cursor position from the actionPopover state
+ const currentValue = displayValue;
+ const cursorPosition = actionPopover.cursorPosition || 0;
+ console.log('๐ Current state:', { currentValue, cursorPosition, displayValue });
+
+ const beforeCursor = currentValue.slice(0, cursorPosition);
+ const afterCursor = currentValue.slice(cursorPosition);
+ const lastSlashIndex = beforeCursor.lastIndexOf('/');
+
+ console.log('๐ Cursor analysis:', { beforeCursor, afterCursor, lastSlashIndex });
+
+ if (lastSlashIndex !== -1) {
+ const afterSlash = beforeCursor.slice(lastSlashIndex + 1);
+ console.log('โก After slash:', afterSlash);
+
+ if (!afterSlash.includes(' ') && !afterSlash.includes('\\n')) {
+ // Replace the /query with [Action] text
+ const beforeSlash = currentValue.slice(0, lastSlashIndex);
+ const actionText = `[${actionInfo.label}]`;
+ const newValue = beforeSlash + actionText + " " + afterCursor;
+
+ console.log('๐ Text replacement:', {
+ beforeSlash,
+ actionText,
+ afterCursor,
+ newValue,
+ oldValue: currentValue
+ });
+
+ setDisplayValue(newValue);
+ setValue(newValue);
+
+ console.log('โ Values set, new text should be:', newValue);
+
+ // Set cursor position after the action text and space
+ const newCursorPosition = lastSlashIndex + actionText.length + 1;
+ setTimeout(() => {
+ if (textAreaRef.current) {
+ textAreaRef.current.setSelectionRange(newCursorPosition, newCursorPosition);
+ textAreaRef.current.focus();
+ }
+ }, 0);
+ } else {
+ console.log('โ After slash contains space/newline, not replacing');
+ }
+ } else {
+ console.log('โ No slash found in text before cursor');
+ }
+
+ console.log('๐ฏ Action selected:', actionId, 'at position:', cursorPosition);
+ setActionPopover(prev => ({ ...prev, isOpen: false }));
+ };'''
+
+# Find and replace the function using regex
+content = re.sub(old_function_pattern, new_function, content, flags=re.DOTALL)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Added comprehensive debugging to handleActionSelect")
diff --git a/debug_action_popover.py b/debug_action_popover.py
new file mode 100644
index 0000000000..fbf613116c
--- /dev/null
+++ b/debug_action_popover.py
@@ -0,0 +1,61 @@
+import re
+
+# Read the ActionPopover file
+with open('ui/desktop/src/components/ActionPopover.tsx', 'r') as f:
+ content = f.read()
+
+# Add debugging to handleItemClick
+old_handleItemClick = ''' const handleItemClick = (index: number) => {
+ onSelectedIndexChange(index);
+ onSelect(actions[index].id);
+ actions[index].action();
+ onClose();
+ };'''
+
+new_handleItemClick = ''' const handleItemClick = (index: number) => {
+ console.log('๐ฏ ActionPopover: handleItemClick called', { index, actionId: actions[index].id });
+ console.log('๐ ActionPopover: onSelect function:', onSelect);
+ console.log('๐ ActionPopover: About to call onSelect with:', actions[index].id);
+
+ onSelectedIndexChange(index);
+
+ // Call onSelect first - this should trigger handleActionSelect in ChatInput
+ onSelect(actions[index].id);
+
+ console.log('โ ActionPopover: onSelect called successfully');
+
+ // Call the local action (just for logging)
+ actions[index].action();
+
+ console.log('๐ช ActionPopover: Closing popover');
+ onClose();
+ };'''
+
+content = content.replace(old_handleItemClick, new_handleItemClick)
+
+# Also add debugging to the selectAction method in useImperativeHandle
+old_selectAction = ''' selectAction: (index: number) => {
+ if (actions[index]) {
+ onSelect(actions[index].id);
+ actions[index].action();
+ onClose();
+ }
+ },'''
+
+new_selectAction = ''' selectAction: (index: number) => {
+ console.log('โจ๏ธ ActionPopover: selectAction called via keyboard', { index, actionId: actions[index]?.id });
+ if (actions[index]) {
+ console.log('๐ ActionPopover: Calling onSelect from selectAction:', actions[index].id);
+ onSelect(actions[index].id);
+ actions[index].action();
+ onClose();
+ }
+ },'''
+
+content = content.replace(old_selectAction, new_selectAction)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ActionPopover.tsx', 'w') as f:
+ f.write(content)
+
+print("Added comprehensive debugging to ActionPopover")
diff --git a/debug_richtextinput.py b/debug_richtextinput.py
new file mode 100644
index 0000000000..d682b2d5b7
--- /dev/null
+++ b/debug_richtextinput.py
@@ -0,0 +1,97 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Add debugging to the renderContent function
+# Find the while loop that processes actions
+old_while_section = ''' while ((match = actionRegex.exec(value)) !== null) {
+ const beforeAction = value.slice(lastIndex, match.index);
+ const actionLabel = match[1];
+
+ // Add text before action with potential cursor
+ if (beforeAction) {'''
+
+new_while_section = ''' console.log('๐จ RichChatInput renderContent called with value:', value);
+ console.log('๐ Looking for action patterns with regex:', actionRegex);
+
+ while ((match = actionRegex.exec(value)) !== null) {
+ console.log('โ Found action match:', match);
+ const beforeAction = value.slice(lastIndex, match.index);
+ const actionLabel = match[1];
+ console.log('๐ Processing action:', { beforeAction, actionLabel, matchIndex: match.index });
+
+ // Add text before action with potential cursor
+ if (beforeAction) {'''
+
+content = content.replace(old_while_section, new_while_section)
+
+# Add debugging to the action pill creation
+old_pill_creation = ''' // Add the action pill
+ addCursorIfNeeded(currentPos);
+ const actionInfo = ACTION_MAP[actionLabel.toLowerCase().replace(/\\s+/g, '-') as keyof typeof ACTION_MAP] ||
+ { label: actionLabel, icon: };
+
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );'''
+
+new_pill_creation = ''' // Add the action pill
+ addCursorIfNeeded(currentPos);
+ const actionInfo = ACTION_MAP[actionLabel.toLowerCase().replace(/\\s+/g, '-') as keyof typeof ACTION_MAP] ||
+ { label: actionLabel, icon: };
+
+ console.log('๐ท๏ธ Creating action pill:', { actionLabel, actionInfo });
+
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );
+
+ console.log('โ Action pill added to parts array');'''
+
+content = content.replace(old_pill_creation, new_pill_creation)
+
+# Add debugging to the onChange handler
+old_onchange = ''' const handleTextareaChange = useCallback((e: React.ChangeEvent) => {
+ const newValue = e.target.value;
+ const newCursorPos = e.target.selectionStart;
+
+ console.log('RichChatInput: onChange', { newValue, newCursorPos });
+ onChange(newValue, newCursorPos);
+ setCursorPosition(newCursorPos);
+ }, [onChange]);'''
+
+new_onchange = ''' const handleTextareaChange = useCallback((e: React.ChangeEvent) => {
+ const newValue = e.target.value;
+ const newCursorPos = e.target.selectionStart;
+
+ console.log('๐ RichChatInput: onChange', { newValue, newCursorPos });
+ console.log('๐จ Will trigger re-render with new value:', newValue);
+ onChange(newValue, newCursorPos);
+ setCursorPosition(newCursorPos);
+ }, [onChange]);'''
+
+content = content.replace(old_onchange, new_onchange)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Added comprehensive debugging to RichChatInput")
diff --git a/fix_action_popover_order.py b/fix_action_popover_order.py
new file mode 100644
index 0000000000..8c92ebb662
--- /dev/null
+++ b/fix_action_popover_order.py
@@ -0,0 +1,80 @@
+import re
+
+# Read the ActionPopover file
+with open('ui/desktop/src/components/ActionPopover.tsx', 'r') as f:
+ content = f.read()
+
+# Fix the order of operations in handleItemClick - onSelect should happen BEFORE onClose
+old_handleItemClick = ''' const handleItemClick = (index: number) => {
+ console.log('๐ฏ ActionPopover: handleItemClick called', { index, actionId: actions[index].id });
+ console.log('๐ ActionPopover: onSelect function:', onSelect);
+ console.log('๐ ActionPopover: About to call onSelect with:', actions[index].id);
+
+ onSelectedIndexChange(index);
+
+ // Call onSelect first - this should trigger handleActionSelect in ChatInput
+ onSelect(actions[index].id);
+
+ console.log('โ ActionPopover: onSelect called successfully');
+
+ // Call the local action (just for logging)
+ actions[index].action();
+
+ console.log('๐ช ActionPopover: Closing popover');
+ onClose();
+ };'''
+
+new_handleItemClick = ''' const handleItemClick = (index: number) => {
+ console.log('๐ฏ ActionPopover: handleItemClick called', { index, actionId: actions[index].id });
+ console.log('๐ ActionPopover: onSelect function:', onSelect);
+ console.log('๐ ActionPopover: About to call onSelect with:', actions[index].id);
+
+ onSelectedIndexChange(index);
+
+ // Call onSelect first - this should trigger handleActionSelect in ChatInput
+ onSelect(actions[index].id);
+
+ console.log('โ ActionPopover: onSelect called successfully');
+
+ // Call the local action (just for logging)
+ actions[index].action();
+
+ // Close popover after a small delay to allow text replacement to complete
+ console.log('๐ช ActionPopover: Closing popover after delay');
+ setTimeout(() => {
+ onClose();
+ }, 10);
+ };'''
+
+content = content.replace(old_handleItemClick, new_handleItemClick)
+
+# Also fix the selectAction method
+old_selectAction = ''' selectAction: (index: number) => {
+ console.log('โจ๏ธ ActionPopover: selectAction called via keyboard', { index, actionId: actions[index]?.id });
+ if (actions[index]) {
+ console.log('๐ ActionPopover: Calling onSelect from selectAction:', actions[index].id);
+ onSelect(actions[index].id);
+ actions[index].action();
+ onClose();
+ }
+ },'''
+
+new_selectAction = ''' selectAction: (index: number) => {
+ console.log('โจ๏ธ ActionPopover: selectAction called via keyboard', { index, actionId: actions[index]?.id });
+ if (actions[index]) {
+ console.log('๐ ActionPopover: Calling onSelect from selectAction:', actions[index].id);
+ onSelect(actions[index].id);
+ actions[index].action();
+ setTimeout(() => {
+ onClose();
+ }, 10);
+ }
+ },'''
+
+content = content.replace(old_selectAction, new_selectAction)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ActionPopover.tsx', 'w') as f:
+ f.write(content)
+
+print("Fixed ActionPopover to delay onClose() call")
diff --git a/fix_action_popover_state.py b/fix_action_popover_state.py
new file mode 100644
index 0000000000..8fc01c7a0e
--- /dev/null
+++ b/fix_action_popover_state.py
@@ -0,0 +1,81 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Fix 1: Update actionPopover state to include cursorPosition
+old_state = ''' const [actionPopover, setActionPopover] = useState<{
+ isOpen: boolean;
+ position: { x: number; y: number };
+ selectedIndex: number;
+ }>({
+ isOpen: false,
+ position: { x: 0, y: 0 },
+ selectedIndex: 0,
+ });'''
+
+new_state = ''' const [actionPopover, setActionPopover] = useState<{
+ isOpen: boolean;
+ position: { x: number; y: number };
+ selectedIndex: number;
+ cursorPosition?: number;
+ }>({
+ isOpen: false,
+ position: { x: 0, y: 0 },
+ selectedIndex: 0,
+ cursorPosition: 0,
+ });'''
+
+content = content.replace(old_state, new_state)
+
+# Fix 2: Update the checkForMention function to set cursorPosition when opening action popover
+# Find the section where action popover is opened
+old_action_open = ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: textAreaRect.left,
+ y: textAreaRect.top,
+ },
+ selectedIndex: 0,
+ });'''
+
+new_action_open = ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: textAreaRect.left,
+ y: textAreaRect.top,
+ },
+ selectedIndex: 0,
+ cursorPosition: cursorPosition,
+ });'''
+
+content = content.replace(old_action_open, new_action_open)
+
+# Fix 3: Update handleActionButtonClick to include cursorPosition
+old_button_click = ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: buttonRect.left,
+ y: buttonRect.top,
+ },
+ selectedIndex: 0,
+ });'''
+
+new_button_click = ''' setActionPopover({
+ isOpen: true,
+ position: {
+ x: buttonRect.left,
+ y: buttonRect.top,
+ },
+ selectedIndex: 0,
+ cursorPosition: textAreaRef.current?.getBoundingClientRect ? 0 : 0, // Will be set by RichChatInput
+ });'''
+
+content = content.replace(old_button_click, new_button_click)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Fixed actionPopover state to include cursorPosition")
diff --git a/fix_action_selection.py b/fix_action_selection.py
new file mode 100644
index 0000000000..d3ca5cfcd4
--- /dev/null
+++ b/fix_action_selection.py
@@ -0,0 +1,90 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Find and replace the handleActionSelect function
+old_function = ''' const handleActionSelect = (actionId: string) => {
+ const actionInfo = getActionInfo(actionId);
+
+ // Replace / with [Action] text inline
+ const currentValue = displayValue;
+ const cursorPosition = 0; // RichChatInput handles cursor position internally
+ const beforeCursor = currentValue.slice(0, cursorPosition);
+ const lastSlashIndex = beforeCursor.lastIndexOf('/');
+
+ if (lastSlashIndex !== -1) {
+ const afterSlash = beforeCursor.slice(lastSlashIndex + 1);
+ if (!afterSlash.includes(' ') && !afterSlash.includes('\\n')) {
+ // Replace / with [Action] text
+ const beforeSlash = currentValue.slice(0, lastSlashIndex);
+ // Simplified approach for RichChatInput
+ const actionText = `[${actionInfo.label}]`;
+ const newValue = beforeSlash + actionText + " ";
+
+ setDisplayValue(newValue);
+ setValue(newValue);
+
+ // Track selected action for pill display
+
+
+ // Set cursor position after the action text
+ setTimeout(() => {
+ if (textAreaRef.current) {
+ // Cursor positioning handled by RichChatInput
+ // Focus handled by RichChatInput
+ }
+ }, 0);
+ }
+ }
+
+ console.log('Action selected:', actionId);
+ setActionPopover(prev => ({ ...prev, isOpen: false }));
+ };'''
+
+new_function = ''' const handleActionSelect = (actionId: string) => {
+ const actionInfo = getActionInfo(actionId);
+
+ // Get current cursor position from the RichChatInput
+ const currentValue = displayValue;
+ const cursorPosition = actionPopover.cursorPosition || 0;
+ const beforeCursor = currentValue.slice(0, cursorPosition);
+ const afterCursor = currentValue.slice(cursorPosition);
+ const lastSlashIndex = beforeCursor.lastIndexOf('/');
+
+ if (lastSlashIndex !== -1) {
+ const afterSlash = beforeCursor.slice(lastSlashIndex + 1);
+ // Check if we're still in the same "word" after the slash
+ if (!afterSlash.includes(' ') && !afterSlash.includes('\\n')) {
+ // Replace the /query with [Action] text
+ const beforeSlash = currentValue.slice(0, lastSlashIndex);
+ const actionText = `[${actionInfo.label}]`;
+ const newValue = beforeSlash + actionText + " " + afterCursor;
+
+ setDisplayValue(newValue);
+ setValue(newValue);
+
+ // Set cursor position after the action text and space
+ const newCursorPosition = lastSlashIndex + actionText.length + 1;
+ setTimeout(() => {
+ if (textAreaRef.current) {
+ textAreaRef.current.setSelectionRange(newCursorPosition, newCursorPosition);
+ textAreaRef.current.focus();
+ }
+ }, 0);
+ }
+ }
+
+ console.log('Action selected:', actionId, 'at position:', cursorPosition);
+ setActionPopover(prev => ({ ...prev, isOpen: false }));
+ };'''
+
+# Replace the function
+content = content.replace(old_function, new_function)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Fixed handleActionSelect function to properly handle cursor position and create action pills")
diff --git a/fix_chatinput.py b/fix_chatinput.py
new file mode 100644
index 0000000000..b726a75984
--- /dev/null
+++ b/fix_chatinput.py
@@ -0,0 +1,106 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# 1. Add RichTextInput import after ActionPill import
+content = re.sub(
+ r'(import ActionPill from \'\.\/ActionPill\';)',
+ r'\1\nimport RichTextInput, { ActionData } from \'./RichTextInput\';',
+ content
+)
+
+# 2. Replace selectedActions state with actions state - find the exact pattern
+selectedactions_pattern = r' // Action pills for visual display\s*const \[selectedActions, setSelectedActions\] = useState>.*?\[\]\);'
+content = re.sub(
+ selectedactions_pattern,
+ ' // Action pills for visual display\n const [actions, setActions] = useState([]);',
+ content,
+ flags=re.DOTALL
+)
+
+# 3. Remove the Selected Actions Pills section completely
+selected_pills_pattern = r'\s*\{\/\* Selected Actions Pills \*\/\}\s*\{selectedActions\.length > 0 && \(\s*
\s*Selected:<\/span>\s*\{selectedActions\.map\(\(action\) => \(\s* handleRemoveAction\(action\.id\)\}\s*\/>\s*\)\)\}\s*<\/div>\s*\)\}'
+content = re.sub(selected_pills_pattern, '', content, flags=re.DOTALL)
+
+# 4. Replace all selectedActions with actions and setSelectedActions with setActions
+content = content.replace('selectedActions', 'actions')
+content = content.replace('setActions([]);', 'setActions([]);') # Fix double replacement
+
+# 5. Update handleActionSelect - find and replace the entire function
+handle_action_select_old = r' const handleActionSelect = \(actionId: string\) => \{[^}]*\};'
+handle_action_select_new = ''' const handleActionSelect = (actionId: string) => {
+ const actionInfo = getActionInfo(actionId);
+
+ // Replace / with [Action] text inline
+ const currentValue = displayValue;
+ const cursorPosition = textAreaRef.current?.selectionStart || 0;
+ const beforeCursor = currentValue.slice(0, cursorPosition);
+ const lastSlashIndex = beforeCursor.lastIndexOf('/');
+
+ if (lastSlashIndex !== -1) {
+ const afterSlash = beforeCursor.slice(lastSlashIndex + 1);
+ if (!afterSlash.includes(' ') && !afterSlash.includes('\\n')) {
+ // Replace / with [Action] text
+ const beforeSlash = currentValue.slice(0, lastSlashIndex);
+ const afterCursor = currentValue.slice(cursorPosition);
+ const actionText = `[${actionInfo.label}]`;
+ const newValue = beforeSlash + actionText + afterCursor;
+
+ setDisplayValue(newValue);
+ setValue(newValue);
+
+ // Track action for pill display
+ const newAction: ActionData = {
+ id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
+ actionId,
+ label: actionInfo.label,
+ icon: actionInfo.icon,
+ position: lastSlashIndex,
+ };
+
+ setActions(prev => [...prev, newAction]);
+
+ // Set cursor position after the action text
+ setTimeout(() => {
+ if (textAreaRef.current) {
+ const newCursorPosition = lastSlashIndex + actionText.length;
+ textAreaRef.current.setSelectionRange(newCursorPosition, newCursorPosition);
+ }
+ }, 0);
+ }
+ }
+
+ console.log('Action selected:', actionId);
+ setActionPopover(prev => ({ ...prev, isOpen: false }));
+ };'''
+
+# Find the function more carefully
+lines = content.split('\n')
+start_idx = -1
+end_idx = -1
+brace_count = 0
+in_function = False
+
+for i, line in enumerate(lines):
+ if 'const handleActionSelect = (actionId: string) => {' in line:
+ start_idx = i
+ in_function = True
+ brace_count = line.count('{') - line.count('}')
+ elif in_function:
+ brace_count += line.count('{') - line.count('}')
+ if brace_count == 0:
+ end_idx = i
+ break
+
+if start_idx != -1 and end_idx != -1:
+ # Replace the function
+ new_lines = lines[:start_idx] + handle_action_select_new.split('\n') + lines[end_idx + 1:]
+ content = '\n'.join(new_lines)
+
+print("ChatInput.tsx updated successfully")
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
diff --git a/fix_correct_tailwind_classes.py b/fix_correct_tailwind_classes.py
new file mode 100644
index 0000000000..dec0cc8ef4
--- /dev/null
+++ b/fix_correct_tailwind_classes.py
@@ -0,0 +1,34 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing Tailwind class names to match the CSS variables...")
+
+# Looking at the CSS, the correct classes should be:
+# - text-text-default (maps to --color-text-default)
+# - text-text-muted (maps to --color-text-muted)
+# - border-text-default (maps to --color-text-default)
+
+# Fix 1: Change placeholder text class
+old_placeholder_class = 'className="text-textMuted pointer-events-none select-none"'
+new_placeholder_class = 'className="text-text-muted pointer-events-none select-none"'
+
+content = content.replace(old_placeholder_class, new_placeholder_class)
+
+# Fix 2: Change cursor border class
+old_cursor_border = 'border-textDefault'
+new_cursor_border = 'border-text-default'
+
+content = content.replace(old_cursor_border, new_cursor_border)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed Tailwind class names:")
+print(" - Changed text-textMuted to text-text-muted")
+print(" - Changed border-textDefault to border-text-default")
+print(" - These should now map to the correct CSS variables")
+print(" - Should work properly in both light and dark modes")
diff --git a/fix_cursor_and_placeholder_styling.py b/fix_cursor_and_placeholder_styling.py
new file mode 100644
index 0000000000..4aab54a444
--- /dev/null
+++ b/fix_cursor_and_placeholder_styling.py
@@ -0,0 +1,34 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor and placeholder styling...")
+
+# Fix 1: Change cursor color from white to grey
+old_cursor_1 = 'className="animate-pulse border-l border-textStandard h-4 inline-block"'
+new_cursor_1 = 'className="animate-pulse border-l border-textSubtle h-4 inline-block"'
+
+content = content.replace(old_cursor_1, new_cursor_1)
+
+# Fix 2: Also fix the cursor at the end of renderContent
+old_cursor_2 = ''
+new_cursor_2 = ''
+
+content = content.replace(old_cursor_2, new_cursor_2)
+
+# Fix 3: Ensure placeholder text is using textPlaceholder (should already be correct)
+# Check if it's already using the right class
+if 'text-textPlaceholder' in content:
+ print("โ Placeholder text already using textPlaceholder class")
+else:
+ print("โ ๏ธ Placeholder text class might need fixing")
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor styling:")
+print(" - Changed from border-textStandard (white) to border-textSubtle (grey)")
+print("โ Placeholder text should already be subtle (text-textPlaceholder)")
diff --git a/fix_cursor_blink_with_css.py b/fix_cursor_blink_with_css.py
new file mode 100644
index 0000000000..ef96d4a989
--- /dev/null
+++ b/fix_cursor_blink_with_css.py
@@ -0,0 +1,29 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor blink by using inline styles instead of Tailwind class...")
+
+# Replace animate-blink with inline style
+old_cursor_1 = 'className="animate-blink border-l-2 border-black h-4 inline-block"'
+new_cursor_1 = 'className="border-l-2 border-black h-4 inline-block" style={{ animation: "blink 1s step-end infinite" }}'
+
+content = content.replace(old_cursor_1, new_cursor_1)
+
+# Also fix the other cursor instance
+old_cursor_2 = ''
+new_cursor_2 = ''
+
+content = content.replace(old_cursor_2, new_cursor_2)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor blinking by using inline CSS animation")
+print(" - Removed animate-blink class (not recognized by Tailwind)")
+print(" - Added inline style: animation: 'blink 1s step-end infinite'")
+print(" - Uses the @keyframes blink defined in main.css")
+print(" - Should now blink properly!")
diff --git a/fix_cursor_line_break_logic.py b/fix_cursor_line_break_logic.py
new file mode 100644
index 0000000000..3286bcc80e
--- /dev/null
+++ b/fix_cursor_line_break_logic.py
@@ -0,0 +1,112 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor positioning logic for line breaks...")
+
+# The issue is in how we're processing character by character
+# We need to handle newlines properly and render text as continuous flow
+# Let's rewrite the text processing logic
+
+old_text_processing = ''' // Add text before action with potential cursor
+ if (beforeAction) {
+ const textParts = [];
+ for (let i = 0; i < beforeAction.length; i++) {
+ const char = beforeAction[i];
+ addCursorIfNeeded(currentPos);
+ textParts.push(char);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textParts}
+
+ );
+ }'''
+
+new_text_processing = ''' // Add text before action with potential cursor
+ if (beforeAction) {
+ // Split text by cursor position to insert cursor at the right spot
+ let textWithCursor = [];
+ for (let i = 0; i < beforeAction.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(beforeAction[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }'''
+
+content = content.replace(old_text_processing, new_text_processing)
+
+# Also fix the remaining text processing
+old_remaining_processing = ''' // Add remaining text with potential cursor
+ const remainingText = value.slice(lastIndex);
+ if (remainingText) {
+ const textParts = [];
+ for (let i = 0; i < remainingText.length; i++) {
+ const char = remainingText[i];
+ addCursorIfNeeded(currentPos);
+ textParts.push(char);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textParts}
+
+ );
+ }'''
+
+new_remaining_processing = ''' // Add remaining text with potential cursor
+ const remainingText = value.slice(lastIndex);
+ if (remainingText) {
+ let textWithCursor = [];
+ for (let i = 0; i < remainingText.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(remainingText[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }'''
+
+content = content.replace(old_remaining_processing, new_remaining_processing)
+
+# Remove the separate addCursorIfNeeded calls since we're handling it inline now
+content = content.replace(' // Add cursor before action if needed\n addCursorIfNeeded(currentPos);', '')
+content = content.replace(' // Add cursor at the end if needed\n addCursorIfNeeded(currentPos);', ''' // Add cursor at the end if needed
+ if (isFocused && cursorPosition === currentPos) {
+ parts.push(
+
+ );
+ }''')
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor positioning logic:")
+print(" - Integrated cursor rendering directly into text processing")
+print(" - Cursor now appears inline with text characters")
+print(" - Should handle line breaks properly")
+print(" - Cursor stays at correct position within the text flow")
diff --git a/fix_cursor_placeholder_order.py b/fix_cursor_placeholder_order.py
new file mode 100644
index 0000000000..fdbb7ff652
--- /dev/null
+++ b/fix_cursor_placeholder_order.py
@@ -0,0 +1,48 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor position to appear before placeholder text...")
+
+# Find the placeholder rendering and fix the cursor positioning
+old_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+
+ {placeholder}
+
+ {isFocused && (
+
+ )}
+
+ );
+ }'''
+
+new_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+ {isFocused && (
+
+ )}
+
+ {placeholder}
+
+
+ );
+ }'''
+
+content = content.replace(old_placeholder_render, new_placeholder_render)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor and placeholder order:")
+print(" - Moved cursor before placeholder text in the DOM")
+print(" - Changed ml-0.5 to mr-0.5 (margin-right instead of margin-left)")
+print(" - Cursor now appears at the beginning of the line")
+print(" - Placeholder text appears after the cursor")
diff --git a/fix_cursor_positioning.py b/fix_cursor_positioning.py
new file mode 100644
index 0000000000..36886ba0cc
--- /dev/null
+++ b/fix_cursor_positioning.py
@@ -0,0 +1,29 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor positioning to align with text...")
+
+# Fix 1: Update cursor styling to match text height and alignment
+old_cursor_1 = 'className="border-l-2 border-black h-4 inline-block" style={{ animation: "blink 1s step-end infinite" }}'
+new_cursor_1 = 'className="border-l-2 border-black inline-block" style={{ animation: "blink 1s step-end infinite", height: "1.2em", marginLeft: "1px" }}'
+
+content = content.replace(old_cursor_1, new_cursor_1)
+
+# Fix 2: Also fix the other cursor instance
+old_cursor_2 = ''
+new_cursor_2 = ''
+
+content = content.replace(old_cursor_2, new_cursor_2)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor positioning:")
+print(" - Removed fixed h-4 height")
+print(" - Added height: '1.2em' to match text line height")
+print(" - Added marginLeft: '1px' for better spacing from text")
+print(" - Cursor should now align properly with text")
diff --git a/fix_cursor_to_black_blinking.py b/fix_cursor_to_black_blinking.py
new file mode 100644
index 0000000000..e3f47b238c
--- /dev/null
+++ b/fix_cursor_to_black_blinking.py
@@ -0,0 +1,33 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Making cursor black and blinking like a regular cursor...")
+
+# Fix 1: Change cursor color from grey to black and use proper blinking animation
+old_cursor_1 = 'className="animate-pulse border-l border-textSubtle h-4 inline-block"'
+new_cursor_1 = 'className="animate-blink border-l-2 border-black h-4 inline-block"'
+
+content = content.replace(old_cursor_1, new_cursor_1)
+
+# Fix 2: Also fix the cursor at the end of renderContent
+old_cursor_2 = ''
+new_cursor_2 = ''
+
+content = content.replace(old_cursor_2, new_cursor_2)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Updated cursor styling:")
+print(" - Changed from border-textSubtle (grey) to border-black")
+print(" - Changed from animate-pulse to animate-blink")
+print(" - Made border thicker (border-l-2) for better visibility")
+print(" - Now behaves like a regular text cursor")
+
+# Now we need to add the custom blink animation to the CSS
+print("\n๐ Note: We need to add the animate-blink class to Tailwind config")
+print(" This will make the cursor blink on/off like a regular text cursor")
diff --git a/fix_cursor_vertical_alignment.py b/fix_cursor_vertical_alignment.py
new file mode 100644
index 0000000000..364860089a
--- /dev/null
+++ b/fix_cursor_vertical_alignment.py
@@ -0,0 +1,30 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing cursor vertical alignment with text...")
+
+# Find all cursor instances and fix their styling
+old_cursor_style = 'style={{ animation: "blink 1s step-end infinite", height: "1.2em", marginLeft: "1px" }}'
+new_cursor_style = 'style={{ animation: "blink 1s step-end infinite", height: "1em", marginLeft: "1px", verticalAlign: "baseline" }}'
+
+content = content.replace(old_cursor_style, new_cursor_style)
+
+# Also need to ensure the cursor aligns with the text line height
+# Let's also adjust the className to help with alignment
+old_cursor_class = 'className="border-l-2 border-black inline-block"'
+new_cursor_class = 'className="border-l-2 border-black inline-block align-baseline"'
+
+content = content.replace(old_cursor_class, new_cursor_class)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed cursor vertical alignment:")
+print(" - Changed height from 1.2em to 1em (matches text height)")
+print(" - Added verticalAlign: 'baseline' to align with text baseline")
+print(" - Added align-baseline class for consistent alignment")
+print(" - Cursor should now be centered with the text line")
diff --git a/fix_dark_mode_cursor_and_text.py b/fix_dark_mode_cursor_and_text.py
new file mode 100644
index 0000000000..40bcb83e97
--- /dev/null
+++ b/fix_dark_mode_cursor_and_text.py
@@ -0,0 +1,33 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing dark mode cursor color and text color...")
+
+# Fix 1: Change cursor from black to theme-aware color
+# Find all cursor instances and replace border-black with border-textDefault
+old_cursor_class = 'className="border-l-2 border-black inline-block align-baseline absolute left-0"'
+new_cursor_class = 'className="border-l-2 border-textDefault inline-block align-baseline absolute left-0"'
+
+content = content.replace(old_cursor_class, new_cursor_class)
+
+# Also fix other cursor instances in the text processing
+content = content.replace('border-l-2 border-black inline-block', 'border-l-2 border-textDefault inline-block')
+
+# Fix 2: Change placeholder text color to match text-textMuted properly
+old_placeholder_color = 'className="text-textMuted pointer-events-none select-none"'
+new_placeholder_color = 'className="text-textMuted pointer-events-none select-none"'
+
+# The placeholder color is already correct, but let's make sure it's using the right class
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed dark mode styling:")
+print(" - Changed cursor from border-black to border-textDefault")
+print(" - Cursor will now be white in dark mode, black in light mode")
+print(" - Placeholder text already uses text-textMuted which adapts to theme")
+print(" - Both cursor and text should now work properly in dark mode")
diff --git a/fix_import_issue.py b/fix_import_issue.py
new file mode 100644
index 0000000000..1ce1be42d3
--- /dev/null
+++ b/fix_import_issue.py
@@ -0,0 +1,38 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Fix the import - change from default import to named import
+old_import = "import RichChatInput, { RichChatInputRef } from './RichChatInput';"
+new_import = "import { RichChatInput, RichChatInputRef } from './RichChatInput';"
+
+content = content.replace(old_import, new_import)
+
+print("Fixed RichChatInput import to use named export")
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+# Also check if we need to fix the RichChatInput export
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ rich_content = f.read()
+
+# Make sure it has both named and default exports
+if 'export default RichChatInput;' not in rich_content:
+ # Add default export if missing
+ rich_content = rich_content.replace(
+ 'RichChatInput.displayName = \'RichChatInput\';',
+ 'RichChatInput.displayName = \'RichChatInput\';\n\nexport default RichChatInput;'
+ )
+
+ with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(rich_content)
+
+ print("Added default export to RichChatInput")
+else:
+ print("RichChatInput already has default export")
+
+print("Import/export issue fixed!")
diff --git a/fix_inline_message_display.py b/fix_inline_message_display.py
new file mode 100644
index 0000000000..a33a409452
--- /dev/null
+++ b/fix_inline_message_display.py
@@ -0,0 +1,87 @@
+import re
+
+# Read the MessageContent file
+with open('ui/desktop/src/components/MessageContent.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing MessageContent to display pills inline...")
+
+# Replace the entire return statement to fix inline display
+old_return = ''' return (
+
+ );'''
+
+content = content.replace(old_container, new_container)
+
+# Also need to fix how text parts are rendered to be truly inline
+# Find the text rendering sections and make sure they're inline
+old_text_span = 'className="whitespace-pre-wrap"'
+new_text_span = 'className="inline whitespace-pre-wrap"'
+
+content = content.replace(old_text_span, new_text_span)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed multi-line cursor positioning:")
+print(" - Changed container from flex to whitespace-pre-wrap")
+print(" - Added leading-relaxed for better line height")
+print(" - Made text spans explicitly inline")
+print(" - Cursor should now stay in correct position on line breaks")
diff --git a/fix_placeholder_behavior.py b/fix_placeholder_behavior.py
new file mode 100644
index 0000000000..12cb6b2504
--- /dev/null
+++ b/fix_placeholder_behavior.py
@@ -0,0 +1,45 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing placeholder behavior to stay visible when focused...")
+
+# Find the renderContent function and fix the placeholder logic
+old_placeholder_logic = ''' const renderContent = useCallback(() => {
+ if (!value.trim() && !isFocused) {
+ return (
+
+ {placeholder}
+
+ );
+ }'''
+
+new_placeholder_logic = ''' const renderContent = useCallback(() => {
+ // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+
+ {placeholder}
+
+ {isFocused && (
+
+ )}
+
+ );
+ }'''
+
+content = content.replace(old_placeholder_logic, new_placeholder_logic)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed placeholder behavior:")
+print(" - Changed from text-textPlaceholder to text-textSubtle (matches icon color)")
+print(" - Placeholder now stays visible when input is focused")
+print(" - Only disappears when user actually types text")
+print(" - Cursor appears alongside placeholder when focused")
+print(" - Uses absolute positioning to overlay cursor on placeholder")
diff --git a/fix_placeholder_color_lighter.py b/fix_placeholder_color_lighter.py
new file mode 100644
index 0000000000..a86b3c0b6a
--- /dev/null
+++ b/fix_placeholder_color_lighter.py
@@ -0,0 +1,22 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Changing placeholder color back to lighter original color...")
+
+# Find the placeholder text and change it back to the original lighter color
+old_placeholder_color = 'className="text-textSubtle pointer-events-none select-none"'
+new_placeholder_color = 'className="text-textPlaceholder pointer-events-none select-none"'
+
+content = content.replace(old_placeholder_color, new_placeholder_color)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed placeholder color:")
+print(" - Changed from text-textSubtle (darker grey) back to text-textPlaceholder (lighter grey)")
+print(" - Now uses the same light color as the original empty text")
+print(" - Should be more subtle and less prominent")
diff --git a/fix_placeholder_color_medium.py b/fix_placeholder_color_medium.py
new file mode 100644
index 0000000000..9e457b7eb1
--- /dev/null
+++ b/fix_placeholder_color_medium.py
@@ -0,0 +1,22 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Changing placeholder to a medium grey color...")
+
+# Find the placeholder text and change it to text-muted (neutral-400)
+old_placeholder_color = 'className="text-textPlaceholder pointer-events-none select-none"'
+new_placeholder_color = 'className="text-textMuted pointer-events-none select-none"'
+
+content = content.replace(old_placeholder_color, new_placeholder_color)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed placeholder color:")
+print(" - Changed from text-textPlaceholder (very light) to text-textMuted (medium grey)")
+print(" - Uses --color-neutral-400 which should be more visible")
+print(" - Should be a good balance between subtle and readable")
diff --git a/fix_placeholder_positioning.py b/fix_placeholder_positioning.py
new file mode 100644
index 0000000000..b94daa0ee5
--- /dev/null
+++ b/fix_placeholder_positioning.py
@@ -0,0 +1,49 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing placeholder positioning and visibility...")
+
+# Find the placeholder rendering logic and fix the positioning
+old_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+
+ {placeholder}
+
+ {isFocused && (
+
+ )}
+
+ );
+ }'''
+
+new_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+
+ {placeholder}
+
+ {isFocused && (
+
+ )}
+
+ );
+ }'''
+
+content = content.replace(old_placeholder_render, new_placeholder_render)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed placeholder positioning:")
+print(" - Removed absolute positioning that was causing cutoff")
+print(" - Added min-h-[1.5em] to ensure proper container height")
+print(" - Added flex items-center for proper vertical alignment")
+print(" - Used ml-0.5 instead of marginLeft for cursor spacing")
+print(" - Placeholder should now be visible and properly positioned")
diff --git a/fix_placeholder_text_shift.py b/fix_placeholder_text_shift.py
new file mode 100644
index 0000000000..1f5c6c06b1
--- /dev/null
+++ b/fix_placeholder_text_shift.py
@@ -0,0 +1,57 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing placeholder text shift when cursor appears...")
+
+# Find the placeholder rendering and fix it to prevent text shifting
+old_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+ {isFocused && (
+
+ )}
+
+ {placeholder}
+
+
+ );
+ }'''
+
+new_placeholder_render = ''' // Show placeholder when there's no text content (regardless of focus state)
+ if (!value.trim()) {
+ return (
+
+
+ {placeholder}
+
+ {isFocused && (
+
+ )}
+
+ );
+ }'''
+
+content = content.replace(old_placeholder_render, new_placeholder_render)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed placeholder text shifting:")
+print(" - Made cursor absolutely positioned so it doesn't affect layout")
+print(" - Added left-0 to position cursor at the start")
+print(" - Added top: 50% and transform: translateY(-50%) for vertical centering")
+print(" - Placeholder text now stays in exact same position when cursor appears")
diff --git a/fix_regex_error.py b/fix_regex_error.py
new file mode 100644
index 0000000000..f952564365
--- /dev/null
+++ b/fix_regex_error.py
@@ -0,0 +1,34 @@
+import re
+
+# Read the MessageContent file
+with open('ui/desktop/src/components/MessageContent.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing regex syntax error in MessageContent...")
+
+# Fix the broken regex - the newline replacement got split
+old_broken_html = ''' dangerouslySetInnerHTML={{
+ __html: part.content
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/`(.*?)`/g, '$1')
+ .replace(/
+/g, ' ')
+ }}'''
+
+new_fixed_html = ''' dangerouslySetInnerHTML={{
+ __html: part.content
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/`(.*?)`/g, '$1')
+ .replace(/\\n/g, ' ')
+ }}'''
+
+content = content.replace(old_broken_html, new_fixed_html)
+
+# Write the modified content back
+with open('ui/desktop/src/components/MessageContent.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed regex syntax error")
+print("Changed .replace(/\\n/g, ' ') - newline regex was broken across lines")
diff --git a/fix_richtextinput_integration.py b/fix_richtextinput_integration.py
new file mode 100644
index 0000000000..dcbf492793
--- /dev/null
+++ b/fix_richtextinput_integration.py
@@ -0,0 +1,43 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Find the RichChatInput onChange handler and update it to handle cursor position
+old_onchange = ''' onChange={(newValue, cursorPos) => {
+ setDisplayValue(newValue);
+ updateValue(newValue);
+ debouncedSaveDraft(newValue);
+ setHasUserTyped(true);
+
+ // Check for mention/action triggers with the new cursor position
+ if (cursorPos !== undefined) {
+ checkForMention(newValue, cursorPos, textAreaRef.current!);
+ }
+ }}'''
+
+new_onchange = ''' onChange={(newValue, cursorPos) => {
+ setDisplayValue(newValue);
+ updateValue(newValue);
+ debouncedSaveDraft(newValue);
+ setHasUserTyped(true);
+
+ // Check for mention/action triggers with the new cursor position
+ if (cursorPos !== undefined) {
+ checkForMention(newValue, cursorPos, {
+ getBoundingClientRect: () => textAreaRef.current?.getBoundingClientRect?.() || new DOMRect(),
+ selectionStart: cursorPos,
+ selectionEnd: cursorPos,
+ value: newValue,
+ } as HTMLTextAreaElement);
+ }
+ }}'''
+
+content = content.replace(old_onchange, new_onchange)
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Fixed RichChatInput onChange integration with cursor position handling")
diff --git a/fix_richtextinput_processing_loop.py b/fix_richtextinput_processing_loop.py
new file mode 100644
index 0000000000..71f257a667
--- /dev/null
+++ b/fix_richtextinput_processing_loop.py
@@ -0,0 +1,169 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing RichChatInput processing loop to handle both actions and mentions...")
+
+# Find the entire processing loop section and replace it
+# Look for the section that starts with "Process all matches in order"
+old_processing_section = ''' // Process all matches in order
+ for (const matchData of allMatches) {
+ const { type, match, index, length, content } = matchData;
+ console.log('โ Found action match:', match);
+ const beforeAction = value.slice(lastIndex, match.index);
+ const actionLabel = match[1];
+ console.log('๐ Processing action:', { beforeAction, actionLabel, matchIndex: match.index });
+
+ // Add text before action with potential cursor
+ if (beforeAction) {
+ // Split text by cursor position to insert cursor at the right spot
+ let textWithCursor = [];
+ for (let i = 0; i < beforeAction.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(beforeAction[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }
+
+
+
+ // Find matching action
+ const actionEntry = Object.entries(ACTION_MAP).find(
+ ([_, config]) => config.label === actionLabel
+ );
+
+ if (actionEntry) {
+ const [actionId, config] = actionEntry;
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );
+ } else {
+ // If no matching action, render as text
+ parts.push(
+
+ {match[0]}
+
+ );
+ }
+
+ currentPos += match[0].length;
+ lastIndex = match.index + match[0].length;
+ }'''
+
+new_processing_section = ''' // Process all matches in order
+ for (const matchData of allMatches) {
+ const { type, match, index, length, content } = matchData;
+ console.log('โ Found match:', { type, content, index });
+
+ // Add text before this match with potential cursor
+ const beforeMatch = value.slice(lastIndex, index);
+ if (beforeMatch) {
+ let textWithCursor = [];
+ for (let i = 0; i < beforeMatch.length; i++) {
+ if (isFocused && cursorPosition === currentPos) {
+ textWithCursor.push(
+
+ );
+ }
+ textWithCursor.push(beforeMatch[i]);
+ currentPos++;
+ }
+
+ parts.push(
+
+ {textWithCursor}
+
+ );
+ }
+
+ // Add cursor before match if needed
+ if (isFocused && cursorPosition === currentPos) {
+ parts.push(
+
+ );
+ }
+
+ if (type === 'action') {
+ // Handle action pills
+ const actionLabel = content;
+ const actionEntry = Object.entries(ACTION_MAP).find(
+ ([_, config]) => config.label === actionLabel
+ );
+
+ console.log('๐ท๏ธ Creating action pill:', { actionLabel, actionEntry });
+
+ if (actionEntry) {
+ const [actionId, config] = actionEntry;
+ parts.push(
+ handleRemoveAction(actionLabel)}
+ />
+ );
+ } else {
+ // If no matching action, render as text
+ parts.push(
+
+ {match[0]}
+
+ );
+ }
+ } else if (type === 'mention') {
+ // Handle mention pills
+ const fileName = content; // filename without @
+ const filePath = `@${fileName}`; // full mention text
+
+ console.log('๐ Creating mention pill:', { fileName, filePath });
+
+ parts.push(
+ handleRemoveMention(fileName)}
+ />
+ );
+ }
+
+ currentPos += length;
+ lastIndex = index + length;
+ }'''
+
+content = content.replace(old_processing_section, new_processing_section)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed RichChatInput processing loop:")
+print(" - Updated to use new combined action/mention processing")
+print(" - Added proper mention pill creation")
+print(" - Added debugging logs for both types")
+print(" - Should now render @filename as green pills")
diff --git a/fix_selection_alignment.py b/fix_selection_alignment.py
new file mode 100644
index 0000000000..87b591eca9
--- /dev/null
+++ b/fix_selection_alignment.py
@@ -0,0 +1,118 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing selection alignment - making hidden textarea match visual display exactly...")
+
+# The issue is that the hidden textarea and visual display have different positioning/padding
+# We need to make them align perfectly
+
+# Update the hidden textarea to match the visual display exactly
+old_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.1, // More visible for selection
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'rgba(59, 130, 246, 0.1)', // Very light blue text
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ fontFamily: 'inherit',
+ fontSize: 'inherit',
+ lineHeight: 'inherit',
+ padding: 'inherit',
+ }}'''
+
+new_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.15, // Slightly more visible for selection
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'rgba(59, 130, 246, 0.2)', // Slightly more visible blue text
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ fontFamily: 'Cash Sans, sans-serif', // Match exact font
+ fontSize: '0.875rem', // Match text-sm (14px)
+ lineHeight: '1.5', // Match leading-relaxed
+ padding: '0', // No padding to match visual display
+ margin: '0',
+ boxSizing: 'border-box',
+ whiteSpace: 'pre-wrap', // Match visual display
+ wordWrap: 'break-word',
+ }}'''
+
+content = content.replace(old_hidden_textarea_style, new_hidden_textarea_style)
+
+# Also need to make sure the visual display has consistent styling
+old_visual_display = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+new_visual_display = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+content = content.replace(old_visual_display, new_visual_display)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed selection alignment:")
+print(" - Matched font family, size, and line height exactly")
+print(" - Removed padding/margin differences")
+print(" - Added consistent white-space and word-wrap")
+print(" - Selection should now align perfectly with visual text")
+print(" - No more 'holographic' offset effect")
diff --git a/fix_selection_visibility.py b/fix_selection_visibility.py
new file mode 100644
index 0000000000..c1d1cca575
--- /dev/null
+++ b/fix_selection_visibility.py
@@ -0,0 +1,124 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing selection visibility to show over visual content...")
+
+# The issue is that selection is happening on the hidden textarea but we need it visible over the visual layer
+# Let's make the hidden textarea slightly more visible and add a selection overlay
+
+# Update the hidden textarea to be slightly more visible during selection
+old_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ }}
+ className="absolute inset-0 w-full h-full resize-none selection:bg-blue-500 selection:text-white"'''
+
+new_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.1, // More visible for selection
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'rgba(59, 130, 246, 0.1)', // Very light blue text
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ fontFamily: 'inherit',
+ fontSize: 'inherit',
+ lineHeight: 'inherit',
+ padding: 'inherit',
+ }}
+ className="absolute inset-0 w-full h-full resize-none selection:bg-blue-500 selection:text-white"'''
+
+content = content.replace(old_hidden_textarea_style, new_hidden_textarea_style)
+
+# Also update the CSS to make selection more prominent
+css_update = '''
+/* Enhanced text selection styling */
+.rich-text-input textarea::selection {
+ background-color: #3b82f6 !important; /* Blue-500 with !important */
+ color: white !important;
+ opacity: 1 !important;
+}
+
+.rich-text-input textarea::-moz-selection {
+ background-color: #3b82f6 !important; /* Blue-500 with !important */
+ color: white !important;
+ opacity: 1 !important;
+}
+
+/* Make sure selection is visible over visual content */
+.rich-text-input {
+ position: relative;
+}
+
+.rich-text-input textarea {
+ mix-blend-mode: multiply; /* Blend with background for better visibility */
+}
+'''
+
+# Update the main.css file
+with open('ui/desktop/src/styles/main.css', 'r') as f:
+ css_content = f.read()
+
+# Replace the existing selection CSS with enhanced version
+old_selection_css = '''/* Text selection styling for RichChatInput */
+.rich-text-input textarea::selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+.rich-text-input textarea::-moz-selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+/* Global selection styling for better visibility */
+::selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}
+
+::-moz-selection {
+ background-color: #3b82f6; /* Blue-500 */
+ color: white;
+}'''
+
+if old_selection_css in css_content:
+ css_content = css_content.replace(old_selection_css, css_update)
+else:
+ css_content += css_update
+
+with open('ui/desktop/src/styles/main.css', 'w') as f:
+ f.write(css_content)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Enhanced selection visibility:")
+print(" - Increased textarea opacity to 0.1 for better selection visibility")
+print(" - Added light blue text color for better contrast")
+print(" - Added !important to selection CSS rules")
+print(" - Added mix-blend-mode for better visual blending")
+print(" - Selection should now be much more visible")
diff --git a/fix_text_selection_behavior.py b/fix_text_selection_behavior.py
new file mode 100644
index 0000000000..f2e9ce7fb9
--- /dev/null
+++ b/fix_text_selection_behavior.py
@@ -0,0 +1,133 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing text selection behavior to work like a normal text input...")
+
+# The main issue is that we need to make the hidden textarea more accessible
+# and reduce interference from the visual display layer
+
+# First, let's update the hidden textarea to be more interactive
+old_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 1,
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ }}'''
+
+new_hidden_textarea_style = ''' style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 2, // Higher z-index to capture mouse events
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ pointerEvents: 'auto', // Ensure it can receive mouse events
+ }}'''
+
+content = content.replace(old_hidden_textarea_style, new_hidden_textarea_style)
+
+# Update the visual display to have lower z-index and not interfere with selection
+old_visual_display = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+new_visual_display = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+content = content.replace(old_visual_display, new_visual_display)
+
+# Remove the handleDisplayClick since we're not using it anymore
+old_handle_display_click = ''' const handleDisplayClick = useCallback((e: React.MouseEvent) => {
+ // Don't focus if clicking on an action pill
+ if ((e.target as Element).closest('.action-pill-wrapper')) {
+ return;
+ }
+
+ // Focus the hidden textarea for text selection
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.focus();
+
+ // For double/triple click, select text appropriately
+ if (e.detail === 2) {
+ // Double click - select word (let browser handle this)
+ setTimeout(() => {
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.select();
+ }
+ }, 0);
+ } else if (e.detail === 3) {
+ // Triple click - select all
+ setTimeout(() => {
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.select();
+ }
+ }, 0);
+ }
+ }
+ }, []);'''
+
+new_handle_display_click = ''' // Removed handleDisplayClick - let the hidden textarea handle all mouse events naturally'''
+
+content = content.replace(old_handle_display_click, new_handle_display_click)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed text selection behavior:")
+print(" - Made hidden textarea capture all mouse events (higher z-index)")
+print(" - Made visual display non-interactive (pointerEvents: 'none')")
+print(" - Removed interfering click handlers")
+print(" - Should now behave like a normal text input for selection")
+print(" - Clicking, dragging, double-click, triple-click should all work normally")
diff --git a/fix_text_selection_functionality.py b/fix_text_selection_functionality.py
new file mode 100644
index 0000000000..8d2f586a11
--- /dev/null
+++ b/fix_text_selection_functionality.py
@@ -0,0 +1,136 @@
+import re
+
+# Read the RichChatInput file
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ content = f.read()
+
+print("Fixing text selection functionality (highlight, double-click, triple-click)...")
+
+# The issue is that we have a hidden textarea and a visual display layer
+# We need to make sure the visual layer doesn't interfere with text selection
+
+# Find the visual display div and update its properties
+old_display_div = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+new_display_div = ''' {/* Visual display with action pills and cursor */}
+
+ {renderContent()}
+
'''
+
+content = content.replace(old_display_div, new_display_div)
+
+# Also need to update the hidden textarea to be more accessible for selection
+old_hidden_textarea = ''' className="absolute inset-0 w-full h-full opacity-0 pointer-events-auto resize-none"
+ style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0,
+ zIndex: 1,
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ }}'''
+
+new_hidden_textarea = ''' className="absolute inset-0 w-full h-full resize-none"
+ style={{
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ height: '100%',
+ opacity: 0.01, // Nearly invisible but still selectable
+ zIndex: 1,
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ resize: 'none',
+ color: 'transparent', // Hide text but keep selection
+ caretColor: 'transparent', // Hide caret (we show our own)
+ }}'''
+
+content = content.replace(old_hidden_textarea, new_hidden_textarea)
+
+# Update handleDisplayClick to handle selection events better
+old_handle_click = ''' const handleDisplayClick = useCallback((e: React.MouseEvent) => {
+ // Don't focus if clicking on an action pill
+ if ((e.target as Element).closest('.action-pill-wrapper')) {
+ return;
+ }
+ hiddenTextareaRef.current?.focus();
+ }, []);'''
+
+new_handle_click = ''' const handleDisplayClick = useCallback((e: React.MouseEvent) => {
+ // Don't focus if clicking on an action pill
+ if ((e.target as Element).closest('.action-pill-wrapper')) {
+ return;
+ }
+
+ // Focus the hidden textarea for text selection
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.focus();
+
+ // For double/triple click, select text appropriately
+ if (e.detail === 2) {
+ // Double click - select word (let browser handle this)
+ setTimeout(() => {
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.select();
+ }
+ }, 0);
+ } else if (e.detail === 3) {
+ // Triple click - select all
+ setTimeout(() => {
+ if (hiddenTextareaRef.current) {
+ hiddenTextareaRef.current.select();
+ }
+ }, 0);
+ }
+ }
+ }, []);'''
+
+content = content.replace(old_handle_click, new_handle_click)
+
+# Write the modified content back
+with open('ui/desktop/src/components/RichChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("โ Fixed text selection functionality:")
+print(" - Added userSelect: 'text' to visual display")
+print(" - Made hidden textarea slightly visible (opacity: 0.01)")
+print(" - Added double-click and triple-click handling")
+print(" - Preserved pill functionality while enabling text selection")
+print(" - Should now support highlighting, double-click, and triple-click")
diff --git a/replace_textarea.py b/replace_textarea.py
new file mode 100644
index 0000000000..5e1c456fbb
--- /dev/null
+++ b/replace_textarea.py
@@ -0,0 +1,60 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Find and replace the textarea element with RichTextInput
+textarea_start = content.find('')
+if textarea_end == -1:
+ print("Textarea end not found")
+ exit(1)
+
+# Extract the old textarea
+old_textarea = content[textarea_start:textarea_end]
+print(f"Found textarea from {textarea_start} to {textarea_end}")
+
+# Create the new RichTextInput component
+new_richtextinput = ''' {
+ setDisplayValue(newValue);
+ setValue(newValue);
+ setActions(newActions);
+ debouncedSaveDraft(newValue);
+ setHasUserTyped(true);
+ checkForMention(newValue, textAreaRef.current?.selectionStart || 0, textAreaRef.current!);
+ }}
+ onKeyDown={handleKeyDown}
+ onPaste={handlePaste}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ onCompositionStart={handleCompositionStart}
+ onCompositionEnd={handleCompositionEnd}
+ placeholder={isRecording ? '' : 'โโ/โโ to navigate messages'}
+ disabled={isUserInputDisabled}
+ style={{
+ maxHeight: `${maxHeight}px`,
+ overflowY: 'auto',
+ opacity: isRecording ? 0 : 1,
+ }}
+ className="w-full outline-none border-none focus:ring-0 bg-transparent px-3 pt-3 pb-1.5 pr-20 text-sm resize-none text-textStandard placeholder:text-textPlaceholder"
+ actions={actions}
+ maxHeight={maxHeight}
+ />'''
+
+# Replace the textarea with RichTextInput
+content = content[:textarea_start] + new_richtextinput + content[textarea_end:]
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("Textarea replaced with RichTextInput successfully")
diff --git a/simple_inline_pills.py b/simple_inline_pills.py
new file mode 100644
index 0000000000..0cc0be2166
--- /dev/null
+++ b/simple_inline_pills.py
@@ -0,0 +1,50 @@
+import re
+
+# Read the ChatInput file
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Simple approach: Just remove the "Selected Actions Pills" section
+# Find the section that starts with {/* Selected Actions Pills */}
+start_marker = '{/* Selected Actions Pills */}'
+start_pos = content.find(start_marker)
+
+if start_pos != -1:
+ # Find the end of this section - look for the closing div and brace
+ # We need to find the matching closing brace for the conditional
+ lines = content[start_pos:].split('\n')
+ brace_count = 0
+ end_line = 0
+
+ for i, line in enumerate(lines):
+ if i == 0: # First line with the comment
+ continue
+
+ # Count braces to find the end of the conditional
+ open_braces = line.count('{')
+ close_braces = line.count('}')
+ brace_count += open_braces - close_braces
+
+ # Look for the end pattern
+ if ')}\n' in line or line.strip() == ')}':
+ end_line = i + 1
+ break
+
+ if end_line > 0:
+ # Calculate the end position
+ end_pos = start_pos + len('\n'.join(lines[:end_line]))
+
+ # Remove the section
+ content = content[:start_pos] + content[end_pos:]
+
+ print(f"Removed Selected Actions Pills section from position {start_pos} to {end_pos}")
+ else:
+ print("Could not find end of Selected Actions Pills section")
+else:
+ print("Selected Actions Pills section not found")
+
+# Write the modified content back
+with open('ui/desktop/src/components/ChatInput.tsx', 'w') as f:
+ f.write(content)
+
+print("ChatInput.tsx updated - Selected Actions Pills section removed")
diff --git a/test_action_flow.py b/test_action_flow.py
new file mode 100644
index 0000000000..1fd1a5c761
--- /dev/null
+++ b/test_action_flow.py
@@ -0,0 +1,67 @@
+# Let's create a simple test to verify the action flow
+print("๐งช Testing Action Flow Components")
+print("=" * 50)
+
+# Check if files exist and have the right structure
+import os
+
+files_to_check = [
+ 'ui/desktop/src/components/ChatInput.tsx',
+ 'ui/desktop/src/components/RichChatInput.tsx',
+ 'ui/desktop/src/components/ActionPill.tsx',
+ 'ui/desktop/src/components/ActionPopover.tsx'
+]
+
+for file_path in files_to_check:
+ if os.path.exists(file_path):
+ print(f"โ {file_path} exists")
+
+ with open(file_path, 'r') as f:
+ content = f.read()
+
+ if 'ChatInput.tsx' in file_path:
+ if 'handleActionSelect' in content:
+ print(" โ handleActionSelect function found")
+ if 'actionPopover' in content:
+ print(" โ actionPopover state found")
+ if 'cursorPosition' in content:
+ print(" โ cursorPosition handling found")
+
+ elif 'RichChatInput.tsx' in file_path:
+ if 'actionRegex' in content:
+ print(" โ actionRegex found")
+ if 'ActionPill' in content:
+ print(" โ ActionPill import found")
+ if 'renderContent' in content:
+ print(" โ renderContent function found")
+
+ elif 'ActionPill.tsx' in file_path:
+ if 'ActionPillProps' in content:
+ print(" โ ActionPillProps interface found")
+
+ elif 'ActionPopover.tsx' in file_path:
+ if 'onSelect' in content:
+ print(" โ onSelect prop found")
+ else:
+ print(f"โ {file_path} missing")
+
+print("\n๐ Key Integration Points:")
+print("1. ChatInput imports RichChatInput โ ")
+print("2. RichChatInput imports ActionPill โ ")
+print("3. ChatInput has handleActionSelect โ ")
+print("4. ActionPopover calls handleActionSelect โ ")
+print("5. RichChatInput renders [Action] as pills โ ")
+
+print("\n๐ฏ Test Plan:")
+print("1. Type '/' in chat input")
+print("2. Action popover should appear")
+print("3. Click an action (e.g., 'Quick Task')")
+print("4. Should see '[Quick Task]' appear as a pill in the input")
+print("5. Cursor should be positioned after the pill")
+
+print("\n๐ Debug Steps:")
+print("1. Open browser dev tools")
+print("2. Look for console logs starting with ๐ฏ, ๐, ๐, etc.")
+print("3. Check if handleActionSelect is called")
+print("4. Check if text replacement happens")
+print("5. Check if RichChatInput re-renders with new value")
diff --git a/test_basic_functionality.py b/test_basic_functionality.py
new file mode 100644
index 0000000000..56df53c110
--- /dev/null
+++ b/test_basic_functionality.py
@@ -0,0 +1,80 @@
+# Let's create a simple test to verify if the basic text replacement is working
+print("๐งช Testing Basic Text Replacement")
+print("=" * 50)
+
+# Read the handleActionSelect function
+with open('ui/desktop/src/components/ChatInput.tsx', 'r') as f:
+ content = f.read()
+
+# Check if the function exists and has the right structure
+if 'handleActionSelect' in content:
+ print("โ handleActionSelect function found")
+
+ # Check for key components
+ if 'setDisplayValue(newValue)' in content:
+ print("โ setDisplayValue call found")
+ if 'setValue(newValue)' in content:
+ print("โ setValue call found")
+ if 'actionInfo.label' in content:
+ print("โ actionInfo.label usage found")
+ if 'lastSlashIndex' in content:
+ print("โ slash detection logic found")
+
+ # Extract the function for analysis
+ start = content.find('const handleActionSelect = (actionId: string) => {')
+ if start != -1:
+ # Find the end of the function
+ brace_count = 0
+ pos = start
+ while pos < len(content):
+ if content[pos] == '{':
+ brace_count += 1
+ elif content[pos] == '}':
+ brace_count -= 1
+ if brace_count == 0:
+ end = pos + 1
+ break
+ pos += 1
+
+ function_code = content[start:end]
+ print(f"\n๐ Function length: {len(function_code)} characters")
+
+ # Check for potential issues
+ if 'cursorPosition = 0' in function_code:
+ print("โ ๏ธ WARNING: cursorPosition hardcoded to 0")
+ if 'actionPopover.cursorPosition' in function_code:
+ print("โ Using actionPopover.cursorPosition")
+ if 'console.log' in function_code:
+ print("โ Debug logging present")
+
+else:
+ print("โ handleActionSelect function not found")
+
+print("\n๐ Checking RichChatInput integration...")
+
+# Check RichChatInput
+with open('ui/desktop/src/components/RichChatInput.tsx', 'r') as f:
+ rich_content = f.read()
+
+if 'actionRegex = /\\[([^\\]]+)\\]/g' in rich_content:
+ print("โ Action regex pattern found")
+if 'ActionPill' in rich_content:
+ print("โ ActionPill component imported")
+if 'renderContent' in rich_content:
+ print("โ renderContent function found")
+
+print("\n๐ฏ Next Steps:")
+print("1. Open browser dev tools")
+print("2. Type '/' in chat input")
+print("3. Click an action")
+print("4. Look for these console messages:")
+print(" - ๐ฏ ActionPopover: handleItemClick called")
+print(" - ๐ฏ handleActionSelect called with:")
+print(" - ๐ Current state:")
+print(" - ๐ Text replacement:")
+print(" - ๐จ RichChatInput renderContent called")
+
+print("\n๐ก If no console logs appear:")
+print("- ActionPopover might not be calling onSelect")
+print("- handleActionSelect might not be connected")
+print("- Check for JavaScript errors in console")
diff --git a/test_mention_functionality.py b/test_mention_functionality.py
new file mode 100644
index 0000000000..2005a6ca00
--- /dev/null
+++ b/test_mention_functionality.py
@@ -0,0 +1,63 @@
+print("๐งช Testing Mention Pill Functionality")
+print("=" * 50)
+
+# Check if all files exist and have the right structure
+import os
+
+files_to_check = [
+ 'ui/desktop/src/components/MentionPill.tsx',
+ 'ui/desktop/src/components/RichChatInput.tsx',
+ 'ui/desktop/src/components/MessageContent.tsx',
+ 'ui/desktop/src/components/ChatInput.tsx'
+]
+
+for file_path in files_to_check:
+ if os.path.exists(file_path):
+ print(f"โ {file_path} exists")
+
+ with open(file_path, 'r') as f:
+ content = f.read()
+
+ if 'MentionPill.tsx' in file_path:
+ if 'MentionPillProps' in content:
+ print(" โ MentionPillProps interface found")
+ if 'fileName' in content and 'filePath' in content:
+ print(" โ fileName and filePath props found")
+
+ elif 'RichChatInput.tsx' in file_path:
+ if 'import MentionPill' in content:
+ print(" โ MentionPill import found")
+ else:
+ print(" โ MentionPill import missing!")
+ if 'mentionRegex' in content:
+ print(" โ mentionRegex found")
+ if 'handleRemoveMention' in content:
+ print(" โ handleRemoveMention function found")
+
+ elif 'MessageContent.tsx' in file_path:
+ if 'import MentionPill' in content:
+ print(" โ MentionPill import found")
+ else:
+ print(" โ MentionPill import missing!")
+ if 'mentionRegex' in content:
+ print(" โ mentionRegex found")
+
+ elif 'ChatInput.tsx' in file_path:
+ if 'handleMentionFileSelect' in content:
+ print(" โ handleMentionFileSelect function found")
+ if '@${fileName}' in content:
+ print(" โ @filename format creation found")
+ else:
+ print(f"โ {file_path} missing")
+
+print("\n๐ฏ Test Plan:")
+print("1. Type '@' in chat input")
+print("2. Select a file from the popover")
+print("3. Should see green pill with filename")
+print("4. Check browser console for debug logs")
+
+print("\n๐ Debug Steps:")
+print("1. Open browser dev tools")
+print("2. Look for console logs starting with ๐, ๐, โ ")
+print("3. Check if MentionPill component renders")
+print("4. Verify @filename pattern is detected")
diff --git a/ui/desktop/src/components/ActionPill.tsx b/ui/desktop/src/components/ActionPill.tsx
new file mode 100644
index 0000000000..e123644f9c
--- /dev/null
+++ b/ui/desktop/src/components/ActionPill.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { X } from 'lucide-react';
+
+interface ActionPillProps {
+ actionId: string;
+ label: string;
+ icon: React.ReactNode;
+ onRemove?: () => void; // Optional for read-only pills in messages
+ variant?: 'default' | 'message'; // Different styles for input vs message display
+ size?: 'sm' | 'md';
+}
+
+export const ActionPill: React.FC = ({
+ label,
+ icon,
+ onRemove,
+ variant = 'default',
+ size = 'sm'
+}) => {
+ const baseClasses = "inline-flex items-center gap-1.5 font-medium border rounded-full";
+
+ const variantClasses = {
+ default: "bg-bgProminent text-textProminentInverse border-borderProminent",
+ message: "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900 dark:text-blue-200 dark:border-blue-700"
+ };
+
+ const sizeClasses = {
+ sm: "px-2 py-1 text-xs",
+ md: "px-3 py-1.5 text-sm"
+ };
+
+ return (
+