Skip to content

fix(desktop): restore native text editing shortcuts in chat and auto-focus on pane nav#2676

Merged
Kitenite merged 5 commits into
superset-sh:mainfrom
michalkopanski:mk/almondine-can
Mar 26, 2026
Merged

fix(desktop): restore native text editing shortcuts in chat and auto-focus on pane nav#2676
Kitenite merged 5 commits into
superset-sh:mainfrom
michalkopanski:mk/almondine-can

Conversation

@michalkopanski
Copy link
Copy Markdown
Contributor

@michalkopanski michalkopanski commented Mar 21, 2026

What does this PR do? (required)

Three related fixes to keyboard/focus behavior in the chat prompt textarea:

  1. Skip pane-nav shortcuts when input is focusedCmd+Shift+Left/Right was being intercepted by the PREV_PANE/NEXT_PANE hotkey handlers when the cursor was in a textarea or input. Added an early-return guard so native text selection is preserved.

  2. Stop modifier+arrow propagation in chat textarea — Moved the fix to the source: the prompt textarea now calls stopPropagation on Cmd/Ctrl+Arrow keys so they perform native text navigation/selection rather than bubbling to pane-navigation handlers.

  3. Auto-focus chat textarea on pane nav — When navigating to a chat pane via keyboard shortcut, the prompt textarea is now automatically focused (mirroring how the terminal auto-focuses on pane activation). The cursor is placed at the end of any existing draft text rather than selecting it all.

Link to Basecamp to-do, Trello card, New Relic or Honeybadger (required)

To be added by developer

QA

What platforms should be included in QA?

  • Desktop web
  • Mobile web
  • iOS
  • Android
  • API
  • N/A — Desktop app only

QA steps

Text editing shortcuts in chat textarea:

  1. Open the desktop app with multiple panes visible (e.g. chat pane + terminal pane).
  2. Click into the chat prompt textarea and type several words.
  3. Place the cursor in the middle of the text.
  4. Press Cmd+Left — verify it moves the cursor to the start of the line (native behavior), not switching to the previous pane.
  5. Press Cmd+Right — verify it moves the cursor to the end of the line, not switching to the next pane.
  6. Press Cmd+Shift+Left — verify it selects text to the left of the cursor, not switching panes.
  7. Press Cmd+Shift+Right — verify it selects text to the right, not switching panes.

Pane navigation still works outside inputs:
8. Click on an empty area (not a textarea/input) and press Cmd+Shift+Left/Right — verify pane navigation still works as expected.

Auto-focus chat textarea on pane nav:
9. With a chat pane and terminal pane open, click into the terminal so it has focus.
10. Press the pane navigation shortcut (Cmd+Shift+Left or Cmd+Shift+Right) to move focus to the chat pane.
11. Verify the chat prompt textarea is automatically focused (you can start typing immediately).
12. If there is existing draft text in the textarea, verify the cursor is placed at the end of the text (not selecting all).

Screenshots (if appropriate)

N/A

Docs

Update changelog after deployment if the changes in Admin are valuable for Sales, Support or Success teams.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced focus management for the prompt input when switching between panes
    • Improved text cursor positioning when focus is restored
    • Fixed handling of modifier key combinations (Ctrl/Cmd + arrow keys) in text input for better keyboard navigation

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5420c51-7506-494a-8c0a-4c6e356e28ca

📥 Commits

Reviewing files that changed from the base of the PR and between 1259b9b and 4a5223a.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
  • apps/desktop/src/renderer/components/Chat/ChatInterface/hooks/useFocusPromptOnPane.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx

📝 Walkthrough

Walkthrough

Programmatic focus was added to move the prompt textarea cursor to the end; modifier+ArrowLeft/ArrowRight keystrokes now stop propagation. A new hook ties pane focus state to focusing the prompt input, and relevant ChatInputFooter components invoke that hook.

Changes

Cohort / File(s) Summary
Prompt input component
packages/ui/src/components/ai-elements/prompt-input.tsx
PromptInputProvider.focus now guards against null, calls el.focus() and sets caret to end with setSelectionRange(len, len). PromptInputTextarea.handleKeyDown stops propagation for ArrowLeft/ArrowRight when metaKey or ctrlKey is held.
Focus hook (new)
apps/desktop/src/renderer/components/Chat/ChatInterface/hooks/useFocusPromptOnPane.ts
Added exported useFocusPromptOnPane(isFocused: boolean) hook that reads textInput from usePromptInputController() and focuses it via useEffect when isFocused becomes true.
Chat input footers
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx, apps/desktop/src/renderer/routes/.../WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
Imported and invoked useFocusPromptOnPane(isFocused) in both ChatInputFooter implementations to trigger prompt focus when pane focus changes.

Sequence Diagram(s)

sequenceDiagram
  participant Pane as Pane / isFocused
  participant Footer as ChatInputFooter
  participant Hook as useFocusPromptOnPane
  participant Controller as PromptInputController
  participant Textarea as Prompt Textarea

  Pane->>Footer: isFocused changes (true)
  Footer->>Hook: call useFocusPromptOnPane(isFocused)
  Hook->>Controller: read textInput ref
  Hook->>Textarea: invoke textInput.focus()
  Textarea-->>Hook: focused (caret moved to end via setSelectionRange)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I twitch my whiskers, hop to the end,
I nudge the caret where lines bend,
Cmd and Ctrl, I hush your stride,
Arrows stay put, no higher tide,
A gentle focus—hop, and send!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: restoring native text editing shortcuts in chat and adding auto-focus on pane navigation.
Description check ✅ Passed The description provides clear context for all three fixes, includes detailed QA steps, and specifies desktop-app-only scope, though external links and changelog notes are noted as pending.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@michalkopanski michalkopanski marked this pull request as ready for review March 22, 2026 23:29
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)

337-344: Consider extracting the editable-element guard to a reusable helper.

The guard logic is duplicated across both PREV_PANE and NEXT_PANE handlers. Given the context that each component using useAppHotkey must implement its own checks for editable elements, extracting this to a utility would improve consistency and reusability.

♻️ Proposed refactor

Add a helper function near the top of the file or in a shared utilities module:

function isEditableElement(target: EventTarget | null): boolean {
  if (!target || !(target instanceof Element)) return false;
  const tagName = target.tagName;
  return (
    tagName === "TEXTAREA" ||
    tagName === "INPUT" ||
    (target as HTMLElement).isContentEditable
  );
}

Then simplify both handlers:

 useAppHotkey(
   "PREV_PANE",
   (event) => {
-    const target = event.target as Element;
-    if (
-      target?.tagName === "TEXTAREA" ||
-      target?.tagName === "INPUT" ||
-      (target as HTMLElement)?.isContentEditable
-    )
-      return;
+    if (isEditableElement(event.target)) return;
     if (!activeTabId || !activeTab?.layout || !focusedPaneId) return;

Also applies to: 357-364

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx
around lines 337 - 344, Extract the duplicated editable-element guard used in
the PREV_PANE and NEXT_PANE handlers into a reusable helper (e.g.,
isEditableElement) and use it from both handlers: create
isEditableElement(target: EventTarget | null) that returns true when target is
an Element and its tagName is "TEXTAREA" or "INPUT" or isContentEditable is
true, place it near the top of the file or in a shared util, and replace the
inline guard checks inside the PREV_PANE and NEXT_PANE callbacks (the handlers
passed to useAppHotkey) with calls to this helper to ensure consistent behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx:
- Around line 337-344: Extract the duplicated editable-element guard used in the
PREV_PANE and NEXT_PANE handlers into a reusable helper (e.g.,
isEditableElement) and use it from both handlers: create
isEditableElement(target: EventTarget | null) that returns true when target is
an Element and its tagName is "TEXTAREA" or "INPUT" or isContentEditable is
true, place it near the top of the file or in a shared util, and replace the
inline guard checks inside the PREV_PANE and NEXT_PANE callbacks (the handlers
passed to useAppHotkey) with calls to this helper to ensure consistent behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba082e76-7bdf-4aeb-92cf-f5dc52bc8ff7

📥 Commits

Reviewing files that changed from the base of the PR and between da16156 and 3aea57f.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx

@Kitenite
Copy link
Copy Markdown
Collaborator

I don't think this is exactly right since the hotkeys can be rebound which would make this stale. wonder if we need to fix hotkeys at the api level or if we should fix the chat input to prevent bubbling to hotkeys instead ?

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx">

<violation number="1">
P1: Pane navigation hotkeys lost editable-element guards, so PREV_PANE/NEXT_PANE can now trigger while typing in inputs/contentEditable fields.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Cmd+Shift+Left/Right should allow text selection in chat prompts, not
hijack focus to the adjacent pane.
Fix pane-nav hotkey hijacking at the source — the prompt input now calls
stopPropagation for Cmd/Ctrl+Arrow keys so they perform native text
selection rather than switching panes.
@michalkopanski michalkopanski changed the title fix(desktop): skip pane-nav shortcuts when input/textarea is focused fix(desktop): restore native text editing shortcuts in chat and auto-focus on pane nav Mar 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx (1)

85-85: Implementation is correct; consider extracting shared focus logic.

The useEffect and usePromptInputController usage is correct. However, this code is nearly identical to apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx. The focus-on-pane logic could be extracted into a small custom hook (e.g., useFocusPromptOnPane) to reduce duplication and ensure both components stay in sync.

♻️ Example hook extraction

Create a shared hook:

// e.g., in a shared hooks directory
export function useFocusPromptOnPane(isFocused: boolean) {
  const { textInput } = usePromptInputController();
  
  useEffect(() => {
    if (isFocused) {
      textInput.focus();
    }
  }, [isFocused, textInput]);
  
  return { textInput };
}

Then both ChatInputFooter components can simply call:

const { textInput } = useFocusPromptOnPane(isFocused);

Also applies to: 93-97

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
at line 85, Duplicate focus-on-pane logic in WorkspaceChatInterface's
ChatInputFooter (useEffect + usePromptInputController usage) should be extracted
into a shared custom hook (e.g., useFocusPromptOnPane) so both ChatInputFooter
implementations stay in sync; create a small hook that accepts isFocused, calls
usePromptInputController() inside, and runs the existing useEffect to call
textInput.focus() when isFocused is true, then replace the local
textInput/useEffect usage in WorkspaceChatInterface's ChatInputFooter and the
other ChatInputFooter (also referenced around the 93-97 block) to simply call
const { textInput } = useFocusPromptOnPane(isFocused).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx:
- Line 85: Duplicate focus-on-pane logic in WorkspaceChatInterface's
ChatInputFooter (useEffect + usePromptInputController usage) should be extracted
into a shared custom hook (e.g., useFocusPromptOnPane) so both ChatInputFooter
implementations stay in sync; create a small hook that accepts isFocused, calls
usePromptInputController() inside, and runs the existing useEffect to call
textInput.focus() when isFocused is true, then replace the local
textInput/useEffect usage in WorkspaceChatInterface's ChatInputFooter and the
other ChatInputFooter (also referenced around the 93-97 block) to simply call
const { textInput } = useFocusPromptOnPane(isFocused).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adf5ed90-7941-40ba-b8fe-24a1d2c55ff5

📥 Commits

Reviewing files that changed from the base of the PR and between 26c7ecb and 1259b9b.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceChat/components/WorkspaceChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
  • packages/ui/src/components/ai-elements/prompt-input.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/ai-elements/prompt-input.tsx

@michalkopanski
Copy link
Copy Markdown
Contributor Author

Hey @Kitenite this is ready for review now. I opted to prevent bubbling to hotkeys — good call!

@Kitenite Kitenite merged commit afa0fab into superset-sh:main Mar 26, 2026
13 of 15 checks passed
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