Skip to content

Conversation

@Micro66
Copy link
Collaborator

@Micro66 Micro66 commented Nov 18, 2025

Summary

  • Implement line formatting that preserves indentation and spacing
  • Add auto-focus functionality for better user experience
  • Replace onChange with onInput for real-time formatting
  • Support maintaining leading whitespace when creating new lines
  • Improve text display with pre-wrap and word-wrap styling

Test plan

  • Test line formatting with indentation preservation
  • Verify auto-focus functionality works correctly
  • Test Shift+Enter for new line creation
  • Verify Enter key still sends message on desktop
  • Test mobile input behavior
  • Verify whitespace preservation across multiple lines

Summary by CodeRabbit

  • New Features

    • Chat input gains intelligent auto-indentation: Tab inserts indentation, Shift+Enter (and mobile Enter) preserves leading whitespace on new lines, while Enter (desktop) sends the message.
    • Mobile Tab toolbar added for inserting tabs.
    • Auto-focus on open chat for faster typing.
    • Multiline rendering improved with pre-wrap and better word-break for clearer messages.
  • Bug Fixes

    • Backspace now correctly removes auto-indented whitespace when appropriate.

- Implement line formatting that preserves indentation and spacing
- Add auto-focus functionality for better user experience
- Replace onChange with onInput for real-time formatting
- Support maintaining leading whitespace when creating new lines
- Improve text display with pre-wrap and word-wrap styling
@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Walkthrough

Adds a textarea ref with autofocus, implements onKeyDown handling for Tab, Backspace, Enter and Shift+Enter to preserve indentation and manage caret, attaches the ref to TextareaAutosize, adds mobile Tab toolbar and multiline rendering styles.

Changes

Cohort / File(s) Change Summary
ChatInput component
frontend/src/features/tasks/components/ChatInput.tsx
Added useRef/useEffect; created textareaRef and autofocus-on-mount when enabled; implemented handleKeyDown to insert Tab, remove auto-indent on Backspace, and insert newline preserving current-line indentation for Shift+Enter (desktop Enter sends message); added handleInsertTab for mobile toolbar; attached ref to TextareaAutosize; switched primary key handling to onKeyDown while retaining onKeyPress/onChange; added whiteSpace: 'pre-wrap' and wordWrap: 'break-word' styles; added mobile Tab button toolbar.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Textarea as TextareaAutosize
    participant ChatInput
    participant DOM as textareaRef (DOM)

    User->>Textarea: Type / press keys
    Textarea->>ChatInput: onKeyDown(event)
    alt Tab pressed
        ChatInput->>DOM: insert "\t" at cursor, update value, set caret
    else Backspace at auto-indent
        ChatInput->>DOM: remove leading whitespace, update value, set caret
    else Shift+Enter pressed
        ChatInput->>DOM: compute current line indent -> insert "\n" + indent, update value, set caret
    else Enter (desktop, no Shift)
        ChatInput->>ChatInput: trigger send message
    else Other keys
        ChatInput-->>Textarea: forward to onChange / onKeyPress handlers
    end
    Note over ChatInput,DOM: On mount ChatInput -> focus textareaRef if not disabled
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify caret/selection math when inserting/removing whitespace.
  • Check interactions and ordering between onKeyDown, onKeyPress, and onChange.
  • Confirm autofocus behavior with disabled state and lifecycle edge cases.
  • Test mobile Tab toolbar and multiline rendering styles across devices.

Possibly related PRs

Suggested reviewers

  • feifei325

Poem

🐰
I tapped the keys with velvet paws,
Shift and Enter sow tidy rows.
The cursor hops, the lines align,
Soft pre-wrap makes the paragraphs fine.
A carrot cheer for code that flows. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding markdown-like line formatting support to the chat input component, which aligns with the implementation of auto-indentation, whitespace preservation, and improved multi-line rendering.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch weagent/input-formatting-support

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f6c58a and b112eda.

📒 Files selected for processing (1)
  • frontend/src/features/tasks/components/ChatInput.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/features/tasks/components/ChatInput.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7327f and ad1e764.

📒 Files selected for processing (1)
  • frontend/src/features/tasks/components/ChatInput.tsx (4 hunks)
🔇 Additional comments (6)
frontend/src/features/tasks/components/ChatInput.tsx (6)

7-7: LGTM: Required hooks imported correctly.

The additions of useRef and useEffect are necessary for the new auto-focus and textarea reference functionality.


32-32: LGTM: Textarea ref properly typed.

The ref is correctly typed for HTMLTextAreaElement and will be used for focus management and potential cursor positioning.


89-93: Verify: Re-focus when disabled becomes false?

The dependency array [disabled] means the effect runs whenever disabled changes. If disabled transitions from truefalse (e.g., chat becomes available after loading), the textarea will auto-focus again. This might be intentional UX, but it's non-standard—typically auto-focus only happens on mount with an empty [] dependency array.

If refocusing on enable is desired, the current code is correct. Otherwise, change to:

-  }, [disabled]);
+  }, []);

and remove the !disabled condition inside, or keep it to avoid focusing when mounted in a disabled state.


98-98: LGTM: Ref correctly attached.

The textareaRef is properly forwarded to the TextareaAutosize component for focus and cursor management.


100-100: LGTM: onInput is appropriate for real-time formatting.

While onChange and onInput are functionally similar in React (both fire on every keystroke), onInput is the native DOM event and aligns with the PR intent for real-time formatting. This change is safe and works correctly with the existing composition event handlers.


109-109: LGTM: Styles correctly preserve formatted text.

whiteSpace: 'pre-wrap' preserves leading/trailing spaces and newlines while allowing wrapping, and wordWrap: 'break-word' prevents long tokens from overflowing. These are essential for displaying the indented multiline content correctly.

- Replace problematic onInput handler with proper onKeyDown approach
- Implement auto-indentation only on Shift+Enter key combination
- Preserve whitespace and normal input behavior for all other keys
- Fix cursor positioning after auto-indentation
- Maintain existing Enter key behavior for message sending
- Handle Tab key to insert \t placeholder instead of default focus switching
- Prevent default Tab behavior and insert tab character at cursor position
- Maintain proper cursor positioning after tab insertion
- Preserve existing Shift+Enter auto-indentation functionality
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/src/features/tasks/components/ChatInput.tsx (1)

106-110: Auto-focus re-triggers on every disabled change.

The current effect will re-focus the textarea whenever disabled transitions from true to false, potentially interrupting user interactions elsewhere. If the intent is only to auto-focus on initial mount, use an empty dependency array.

If auto-focus should only occur on mount:

-  // Auto-focus on mount
   useEffect(() => {
     if (textareaRef.current && !disabled) {
       textareaRef.current.focus();
     }
-  }, [disabled]);
+  }, []);

If re-focusing when re-enabled is intentional, the current implementation is correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fb7b24 and 721db18.

📒 Files selected for processing (1)
  • frontend/src/features/tasks/components/ChatInput.tsx (3 hunks)
🔇 Additional comments (3)
frontend/src/features/tasks/components/ChatInput.tsx (3)

7-7: LGTM!

The additional imports (useRef, useEffect) are correctly added and used in the component.


32-32: LGTM!

The textareaRef is properly typed and initialized.


115-129: LGTM!

The ref attachment (line 115), dual keyboard handlers (lines 120-121), and styling updates (line 129) are correctly implemented. The whiteSpace: 'pre-wrap' and wordWrap: 'break-word' styles properly support the whitespace preservation objective.

Comment on lines +55 to +76
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Handle Tab key - insert \t placeholder instead of default tab behavior
if (e.key === 'Tab' && !disabled && !isComposing) {
e.preventDefault();

const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;

// Insert \t placeholder at cursor position
const newValue = value.substring(0, start) + '\t' + value.substring(end);

setMessage(newValue);

// Set cursor position after the inserted tab
setTimeout(() => {
textarea.selectionStart = start + 1;
textarea.selectionEnd = start + 1;
}, 0);
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Use textareaRef.current in cursor restoration callback.

After setMessage triggers a parent re-render (line 68), the local textarea variable captured at line 60 may reference a stale DOM node. The setTimeout callback should access textareaRef.current instead to guarantee cursor restoration on the current element.

Apply this diff to fix cursor restoration:

-      // Set cursor position after the inserted tab
-      setTimeout(() => {
-        textarea.selectionStart = start + 1;
-        textarea.selectionEnd = start + 1;
-      }, 0);
+      // Set cursor position after the inserted tab
+      requestAnimationFrame(() => {
+        if (textareaRef.current) {
+          textareaRef.current.selectionStart = start + 1;
+          textareaRef.current.selectionEnd = start + 1;
+        }
+      });

Verify: Tab insertion feature is not mentioned in PR objectives.

The PR objectives describe line formatting, auto-focus, and Shift+Enter support, but don't mention Tab key handling. Confirm this feature is intentional and within scope.


🏁 Script executed:

#!/bin/bash
# Examine the full ChatInput.tsx component to understand structure and ref usage
cat frontend/src/features/tasks/components/ChatInput.tsx

Length of output: 4308


🏁 Script executed:

#!/bin/bash
# Search for textareaRef definition and usage in the component file
rg -n "textareaRef" frontend/src/features/tasks/components/ChatInput.tsx

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Check for any PR description or objectives
fd -e md -e txt . --type f | xargs grep -l "Tab\|tab" 2>/dev/null | head -5

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Search for PR metadata (if available in git)
git log --all --oneline --grep="Tab\|tab" 2>/dev/null | head -10

Length of output: 421


Use textareaRef.current for cursor restoration instead of stale event target reference.

Lines 71–74 and 95–98 capture the textarea reference from e.target, then call setMessage() which triggers a parent re-render before restoring cursor position. While the event target reference remains valid, using textareaRef.current is more robust and follows React patterns by always referencing the current DOM node through the ref.

Apply this diff to both Tab (lines 71–74) and Shift+Enter (lines 95–98) handlers:

-      // Set cursor position after the inserted tab
-      setTimeout(() => {
-        textarea.selectionStart = start + 1;
-        textarea.selectionEnd = start + 1;
-      }, 0);
+      // Set cursor position after the inserted tab
+      setTimeout(() => {
+        if (textareaRef.current) {
+          textareaRef.current.selectionStart = start + 1;
+          textareaRef.current.selectionEnd = start + 1;
+        }
+      }, 0);

Also fix lines 95–98 similarly for the Shift+Enter handler (start/end positions differ but same pattern applies).


Tab insertion feature is in scope (commit 721db18 confirms intentional addition).

Comment on lines 78 to 103
// Handle Enter key for auto-indentation (only when Shift+Enter)
if (e.key === 'Enter' && e.shiftKey && !disabled && !isComposing) {
e.preventDefault();

const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;

// Get the current line and its leading whitespace
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];
const leadingWhitespace = currentLine.match(/^\s*/)?.[0] || '';

// Insert new line with preserved indentation
const newValue = value.substring(0, start) + '\n' + leadingWhitespace + value.substring(end);

setMessage(newValue);

// Set cursor position after the inserted whitespace
setTimeout(() => {
textarea.selectionStart = start + 1 + leadingWhitespace.length;
textarea.selectionEnd = start + 1 + leadingWhitespace.length;
}, 0);
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use textareaRef.current in cursor restoration callback.

Similar to the Tab handler, after setMessage triggers a re-render (line 95), the local textarea variable captured at line 82 may reference a stale DOM node. The setTimeout callback should access textareaRef.current to ensure reliable cursor restoration.

Apply this diff to fix cursor restoration:

-      // Set cursor position after the inserted whitespace
-      setTimeout(() => {
-        textarea.selectionStart = start + 1 + leadingWhitespace.length;
-        textarea.selectionEnd = start + 1 + leadingWhitespace.length;
-      }, 0);
+      // Set cursor position after the inserted whitespace
+      requestAnimationFrame(() => {
+        if (textareaRef.current) {
+          textareaRef.current.selectionStart = start + 1 + leadingWhitespace.length;
+          textareaRef.current.selectionEnd = start + 1 + leadingWhitespace.length;
+        }
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle Enter key for auto-indentation (only when Shift+Enter)
if (e.key === 'Enter' && e.shiftKey && !disabled && !isComposing) {
e.preventDefault();
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
// Get the current line and its leading whitespace
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];
const leadingWhitespace = currentLine.match(/^\s*/)?.[0] || '';
// Insert new line with preserved indentation
const newValue = value.substring(0, start) + '\n' + leadingWhitespace + value.substring(end);
setMessage(newValue);
// Set cursor position after the inserted whitespace
setTimeout(() => {
textarea.selectionStart = start + 1 + leadingWhitespace.length;
textarea.selectionEnd = start + 1 + leadingWhitespace.length;
}, 0);
}
};
// Handle Enter key for auto-indentation (only when Shift+Enter)
if (e.key === 'Enter' && e.shiftKey && !disabled && !isComposing) {
e.preventDefault();
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
// Get the current line and its leading whitespace
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];
const leadingWhitespace = currentLine.match(/^\s*/)?.[0] || '';
// Insert new line with preserved indentation
const newValue = value.substring(0, start) + '\n' + leadingWhitespace + value.substring(end);
setMessage(newValue);
// Set cursor position after the inserted whitespace
requestAnimationFrame(() => {
if (textareaRef.current) {
textareaRef.current.selectionStart = start + 1 + leadingWhitespace.length;
textareaRef.current.selectionEnd = start + 1 + leadingWhitespace.length;
}
});
}
};
🤖 Prompt for AI Agents
In frontend/src/features/tasks/components/ChatInput.tsx around lines 78 to 103,
the Enter+Shift auto-indent handler captures a local textarea variable and then
uses it inside the setTimeout callback, which can reference a stale DOM node
after setMessage triggers a re-render; replace uses of the captured textarea
inside the timeout with textareaRef.current, guard against null, and set
selectionStart/selectionEnd on textareaRef.current using the computed start + 1
+ leadingWhitespace.length so the cursor restoration is reliable after the
re-render.

- Handle Backspace key to remove auto-indented whitespace efficiently
- Delete entire line of whitespace when cursor is at empty indented line
- Remove all leading whitespace when Backspacing at line start
- Maintain proper cursor positioning after deletion
- Preserve existing Tab and Shift+Enter functionality
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/src/features/tasks/components/ChatInput.tsx (2)

55-76: Tab handler still uses stale textarea reference in cursor restoration.

Despite past review comments being marked as addressed, lines 71-74 still use the captured textarea variable inside setTimeout instead of textareaRef.current. After setMessage(newValue) triggers a parent re-render, the captured reference may be stale.

Apply this diff to fix cursor restoration:

       setMessage(newValue);

       // Set cursor position after the inserted tab
-      setTimeout(() => {
-        textarea.selectionStart = start + 1;
-        textarea.selectionEnd = start + 1;
-      }, 0);
+      requestAnimationFrame(() => {
+        if (textareaRef.current) {
+          textareaRef.current.selectionStart = start + 1;
+          textareaRef.current.selectionEnd = start + 1;
+        }
+      });
       return;
     }

134-159: Shift+Enter handler still uses stale textarea reference in cursor restoration.

Despite past review comments being marked as addressed, lines 154-157 still use the captured textarea variable instead of textareaRef.current in the cursor restoration callback.

Apply this diff to fix cursor restoration:

       setMessage(newValue);

       // Set cursor position after the inserted whitespace
-      setTimeout(() => {
-        textarea.selectionStart = start + 1 + leadingWhitespace.length;
-        textarea.selectionEnd = start + 1 + leadingWhitespace.length;
-      }, 0);
+      requestAnimationFrame(() => {
+        if (textareaRef.current) {
+          textareaRef.current.selectionStart = start + 1 + leadingWhitespace.length;
+          textareaRef.current.selectionEnd = start + 1 + leadingWhitespace.length;
+        }
+      });
     }
   };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 721db18 and 8f6c58a.

📒 Files selected for processing (1)
  • frontend/src/features/tasks/components/ChatInput.tsx (3 hunks)
🔇 Additional comments (4)
frontend/src/features/tasks/components/ChatInput.tsx (4)

7-7: LGTM!

The addition of useRef and useEffect imports is correct and necessary for the auto-focus and cursor management features.


32-32: LGTM!

The ref initialization is properly typed and follows React patterns.


161-166: LGTM!

The auto-focus implementation correctly uses textareaRef.current with proper null-checking and appropriate dependencies.


171-185: LGTM!

The ref attachment, keyboard handler, and style changes correctly implement the multi-line formatting requirements. The whiteSpace: 'pre-wrap' and wordWrap: 'break-word' styles properly preserve indentation and handle text wrapping.

Comment on lines +78 to +132
// Handle Backspace key - remove auto-indented whitespace
if (e.key === 'Backspace' && !disabled && !isComposing) {
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;

// Only handle when cursor is at a single position (no selection)
if (start === end && start > 0) {
// Get the current line and previous character
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];

// Check if we're at the beginning of a line and the line contains only whitespace
if (currentLine.trim() === '' && currentLine.length > 0) {
e.preventDefault();

// Remove the entire line of whitespace and the newline character
const previousLines = lines.slice(0, -1);
const remainingText = value.substring(start);
const newValue = previousLines.join('\n') + (previousLines.length > 0 ? '\n' : '') + remainingText;

setMessage(newValue);

// Set cursor position at the end of the previous line
const newCursorPos = previousLines.length > 0 ? previousLines.join('\n').length + 1 : 0;
setTimeout(() => {
textarea.selectionStart = newCursorPos;
textarea.selectionEnd = newCursorPos;
}, 0);
return;
}

// Check if we're deleting whitespace that was auto-indented
const charBeforeCursor = value.substring(start - 1, start);
if (charBeforeCursor === ' ' || charBeforeCursor === '\t') {
// Get the line start position
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
const lineContent = value.substring(lineStart, start);

// If the line contains only whitespace up to cursor, remove all whitespace
if (lineContent.trim() === '') {
e.preventDefault();
const newValue = value.substring(0, lineStart) + value.substring(start);
setMessage(newValue);

setTimeout(() => {
textarea.selectionStart = lineStart;
textarea.selectionEnd = lineStart;
}, 0);
return;
}
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Backspace handler uses stale textarea reference in cursor restoration.

The Backspace handler has the same cursor restoration issue as the Tab handler. Lines 104-107 and 124-127 use the captured textarea variable in setTimeout callbacks after calling setMessage, which may reference a stale DOM node.

Apply this diff to fix both cursor restoration points:

           setMessage(newValue);

           // Set cursor position at the end of the previous line
           const newCursorPos = previousLines.length > 0 ? previousLines.join('\n').length + 1 : 0;
-          setTimeout(() => {
-            textarea.selectionStart = newCursorPos;
-            textarea.selectionEnd = newCursorPos;
-          }, 0);
+          requestAnimationFrame(() => {
+            if (textareaRef.current) {
+              textareaRef.current.selectionStart = newCursorPos;
+              textareaRef.current.selectionEnd = newCursorPos;
+            }
+          });
           return;
         }

And for the second cursor restoration:

             setMessage(newValue);

-            setTimeout(() => {
-              textarea.selectionStart = lineStart;
-              textarea.selectionEnd = lineStart;
-            }, 0);
+            requestAnimationFrame(() => {
+              if (textareaRef.current) {
+                textareaRef.current.selectionStart = lineStart;
+                textareaRef.current.selectionEnd = lineStart;
+              }
+            });
             return;
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle Backspace key - remove auto-indented whitespace
if (e.key === 'Backspace' && !disabled && !isComposing) {
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
// Only handle when cursor is at a single position (no selection)
if (start === end && start > 0) {
// Get the current line and previous character
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];
// Check if we're at the beginning of a line and the line contains only whitespace
if (currentLine.trim() === '' && currentLine.length > 0) {
e.preventDefault();
// Remove the entire line of whitespace and the newline character
const previousLines = lines.slice(0, -1);
const remainingText = value.substring(start);
const newValue = previousLines.join('\n') + (previousLines.length > 0 ? '\n' : '') + remainingText;
setMessage(newValue);
// Set cursor position at the end of the previous line
const newCursorPos = previousLines.length > 0 ? previousLines.join('\n').length + 1 : 0;
setTimeout(() => {
textarea.selectionStart = newCursorPos;
textarea.selectionEnd = newCursorPos;
}, 0);
return;
}
// Check if we're deleting whitespace that was auto-indented
const charBeforeCursor = value.substring(start - 1, start);
if (charBeforeCursor === ' ' || charBeforeCursor === '\t') {
// Get the line start position
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
const lineContent = value.substring(lineStart, start);
// If the line contains only whitespace up to cursor, remove all whitespace
if (lineContent.trim() === '') {
e.preventDefault();
const newValue = value.substring(0, lineStart) + value.substring(start);
setMessage(newValue);
setTimeout(() => {
textarea.selectionStart = lineStart;
textarea.selectionEnd = lineStart;
}, 0);
return;
}
}
}
}
// Handle Backspace key - remove auto-indented whitespace
if (e.key === 'Backspace' && !disabled && !isComposing) {
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
// Only handle when cursor is at a single position (no selection)
if (start === end && start > 0) {
// Get the current line and previous character
const lines = value.substring(0, start).split('\n');
const currentLine = lines[lines.length - 1];
// Check if we're at the beginning of a line and the line contains only whitespace
if (currentLine.trim() === '' && currentLine.length > 0) {
e.preventDefault();
// Remove the entire line of whitespace and the newline character
const previousLines = lines.slice(0, -1);
const remainingText = value.substring(start);
const newValue = previousLines.join('\n') + (previousLines.length > 0 ? '\n' : '') + remainingText;
setMessage(newValue);
// Set cursor position at the end of the previous line
const newCursorPos = previousLines.length > 0 ? previousLines.join('\n').length + 1 : 0;
requestAnimationFrame(() => {
if (textareaRef.current) {
textareaRef.current.selectionStart = newCursorPos;
textareaRef.current.selectionEnd = newCursorPos;
}
});
return;
}
// Check if we're deleting whitespace that was auto-indented
const charBeforeCursor = value.substring(start - 1, start);
if (charBeforeCursor === ' ' || charBeforeCursor === '\t') {
// Get the line start position
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
const lineContent = value.substring(lineStart, start);
// If the line contains only whitespace up to cursor, remove all whitespace
if (lineContent.trim() === '') {
e.preventDefault();
const newValue = value.substring(0, lineStart) + value.substring(start);
setMessage(newValue);
requestAnimationFrame(() => {
if (textareaRef.current) {
textareaRef.current.selectionStart = lineStart;
textareaRef.current.selectionEnd = lineStart;
}
});
return;
}
}
}
}
🤖 Prompt for AI Agents
In frontend/src/features/tasks/components/ChatInput.tsx around lines 78–132, the
Backspace handler captures the local `textarea` variable and uses it inside
setTimeout callbacks which can reference a stale DOM node; replace those usages
by reading the current textarea from a stable ref (e.g., textareaRef.current) or
by re-querying the element by id inside the timeout, then check for null before
setting selection. Concretely: ensure you store selection positions immediately,
call setMessage, and in the setTimeout use textareaRef.current (or
document.getElementById(savedId)) to restore selection (with null guard) instead
of the captured `textarea`; apply this to both cursor restoration points.

- Enable auto-indentation on mobile Enter key press
- Add mobile Tab button for inserting \t placeholder
- Differentiate Enter key behavior between desktop and mobile
- Mobile: Enter creates new line with auto-indentation
- Desktop: Enter sends message, Shift+Enter creates new line
- Add mobile toolbar with Tab button for better UX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants