Skip to content

fix: emoji input blocked in command palette rename#2291

Closed
pandec wants to merge 3 commits intomanaflow-ai:mainfrom
pandec:fix/emoji-input-command-palette-rename
Closed

fix: emoji input blocked in command palette rename#2291
pandec wants to merge 3 commits intomanaflow-ai:mainfrom
pandec:fix/emoji-input-command-palette-rename

Conversation

@pandec
Copy link
Copy Markdown

@pandec pandec commented Mar 28, 2026

Summary

  • Emoji input could not be used reliably in command palette rename, especially when replacing selected text through the macOS emoji picker.
  • Root cause: the rename field went through the shared SwiftUI/AppKit field-editor path, which could lose a stable text input client after emoji-picker replacement. Subsequent picker interactions no longer targeted the rename field correctly.
  • Fix: move command palette rename onto a dedicated native AppKit NSTextView path and update palette responder ownership and focus handling so the emoji picker stays attached to the rename field.
  • Command palette search is intentionally unchanged. Its detached-picker behavior after selected replacement remains a separate preexisting issue.

Related: #2161

Test plan

  • Open command palette rename for a workspace or tab and insert an emoji from the macOS emoji picker
  • Replace the selected name with an emoji, reopen rename, and repeat selected-text replacement multiple times
  • Verify plain typing, emoji paste, Escape, Enter, and delete-to-exit still work in rename
  • Verify command palette search behavior is unchanged and still tracked separately

(Retested after the greptile/coderabit review fixes as well 👍)


Summary by cubic

Fixes unreliable emoji input in the command palette rename on macOS by moving rename to a dedicated AppKit NSTextView. Restores placeholder and single-line behavior, and tightens focus so the emoji picker stays attached during repeated replacements.

  • Bug Fixes
    • Added a native NSTextView-backed CommandPaletteTextInputRepresentable for rename with placeholder and single-line enforcement, bypassing the shared field editor.
    • Updated the overlay to recognize palette-owned text inputs (field editor or in-view NSTextView), schedule focus without clearing first responder, and removed the periodic focus timer.
    • Normalizes selection after programmatic focus and invalidates IME geometry to keep the emoji picker anchored.
    • Preserves typing, paste, Enter/Escape, and delete-to-exit; search input remains unchanged.

Written for commit 0b07651. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Improved text input responsiveness in the command palette
    • Refined focus management when switching between the palette and main window
    • Enhanced text selection tracking during palette editing operations
  • New Features

    • Reworked rename input to a native text-hosting implementation for more reliable submit/escape/delete and movement handling
    • Expanded recognition of different editable inputs as owned by the palette

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 28, 2026

@pandec is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

Refactors command-palette text input: loosens marked-text responder checks in AppDelegate, replaces SwiftUI TextField with an AppKit-backed NSTextView representable, removes the focus-lock timer, and adds ownership checks via WindowCommandPaletteOverlayController. Selection geometry invalidation and notification handling were updated.

Changes

Cohort / File(s) Summary
AppDelegate responder check
Sources/AppDelegate.swift
Simplified commandPaletteFieldEditorHasMarkedText(in:) to treat any NSTextView as eligible; removed isFieldEditor guard.
Command palette input & focus
Sources/ContentView.swift
Replaced SwiftUI TextField with CommandPaletteTextInputRepresentable + CommandPaletteNativeTextInputView/CommandPaletteNativeTextView; removed focusLockTimer and replaced with scheduled focus work item logic; added ownsTextInputResponder(_:) and switched palette logic to use it.
Selection & input-context updates
Sources/ContentView.swift (same file cohort)
Added selection-geometry invalidation calls (inputContext?.invalidateCharacterCoordinates(), macOS 15.4+ textInputClientDidUpdateSelection()), updated notification handling (NSText.didBeginEditingNotification) and command handling via doCommandBy.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Window
  participant PaletteController
  participant NativeTextView
  participant AppDelegate

  User->>Window: open command palette
  Window->>PaletteController: present overlay
  PaletteController->>Window: scheduleFocusIntoPalette
  Window->>PaletteController: is key & ownsTextInputResponder?
  alt owns responder
    PaletteController->>NativeTextView: allow input (becomes first responder)
    NativeTextView->>NativeTextView: handle commands (doCommandBy)
    NativeTextView->>PaletteController: notify selection/edits
    PaletteController->>NativeTextView: invalidateCharacterCoordinates / textInputClientDidUpdateSelection()
  else does not own
    PaletteController->>AppDelegate: query marked-text via AppDelegate helper
    AppDelegate->>NativeTextView: check hasMarkedText()
    AppDelegate-->>PaletteController: marked-text status
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I swapped a field for native fur and view,
No tick-tock locks—focus hops anew.
I guard the keys, I mend selection lines,
A palette blooming where the cursor shines.
Hooray — a soft, precise input cue!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ 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%. 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 and concisely describes the main fix: resolving emoji input issues in the command palette rename feature.
Description check ✅ Passed The description includes summary of changes and root cause, comprehensive test plan, but is missing demo video URL and review trigger block.

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 28, 2026

Greptile Summary

This PR fixes unreliable emoji input in the command palette rename field on macOS by moving it from the shared SwiftUI/AppKit field-editor path (NSTextField-backed TextField) to a dedicated NSTextView stack (CommandPaletteNativeTextView / CommandPaletteNativeTextInputView / CommandPaletteTextInputRepresentable). The root cause was that the shared field editor could lose its stable text-input client after the macOS emoji picker replaced selected text, breaking subsequent picker interactions.

Key changes:

  • New native NSTextView path for rename: bypasses the shared field editor, gives the emoji picker a stable, non-field-editor NSTextView to stay attached to across multiple replacements.
  • Removed 80 ms focusLockTimer: replaced by the existing one-shot scheduledFocusWorkItem, eliminating periodic responder reassertion that could interrupt the picker.
  • Backgrounded/panel window handling: when window.isKeyWindow is false (e.g. emoji picker has taken key-window status), the code no longer clears first responder. This is the core fix keeping the picker anchored.
  • Responder ownership broadened: isPaletteTextInputFirstResponder and firstEditableTextInputView now recognise both field-editor paths (for search) and the new direct NSTextView path (for rename), without mixing the two.
  • commandPaletteFieldEditorHasMarkedText updated: isFieldEditor check removed so IME-suppression of Escape/Return applies to the new rename view as well.
  • Placeholder, single-line enforcement, invalidateCharacterCoordinates(), and selection normalisation are all preserved or improved.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/naming suggestions with no impact on runtime correctness.

The fix is well-scoped and carefully handles the emoji-picker focus edge cases. The only issues found are: (1) the function commandPaletteFieldEditorHasMarkedText retaining a now-inaccurate name after dropping the isFieldEditor guard, and (2) a theoretical edge case in textDidEndEditing's panel check when the app is backgrounded — both P2 and neither affects the primary fix or existing functionality.

No files require special attention.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Removes the isFieldEditor guard from commandPaletteFieldEditorHasMarkedText so it covers the new native NSTextView rename path; functionally correct but the function name is now misleading.
Sources/ContentView.swift Replaces the SwiftUI TextField-backed rename field with a dedicated NSTextView stack (CommandPaletteNativeTextView / CommandPaletteNativeTextInputView / CommandPaletteTextInputRepresentable), removes the 80 ms focus-lock timer, and tightens focus/responder ownership checks so the macOS emoji picker stays attached during repeated text replacements.

Sequence Diagram

sequenceDiagram
    participant User
    participant EmojiPicker as macOS Emoji Picker (NSPanel)
    participant NativeTV as CommandPaletteNativeTextView
    participant Coordinator as NSTextViewDelegate (Coordinator)
    participant Overlay as WindowCommandPaletteOverlayController
    participant SwiftUI as SwiftUI State

    User->>NativeTV: Opens emoji picker (⌃⌘Space)
    EmojiPicker->>EmojiPicker: Becomes NSApp.keyWindow (NSPanel)
    Note over Overlay: updateFocusLockForWindowState:<br/>!window.isKeyWindow → skip clear first responder<br/>(keeps NativeTV as first responder)
    EmojiPicker->>NativeTV: Inserts emoji (replaces selected text)
    NativeTV->>Coordinator: textDidChange(_:)
    Coordinator->>Coordinator: sanitizedSingleLineText()
    Coordinator->>SwiftUI: parent.text = sanitizedText
    Coordinator->>NativeTV: invalidateCharacterCoordinates()
    Note over NativeTV: Emoji picker stays anchored
    User->>EmojiPicker: Selects another emoji
    EmojiPicker->>NativeTV: Replaces selection again
    Coordinator->>Coordinator: textDidEndEditing check:<br/>NSApp.keyWindow is NSPanel → skip isFocused=false
    Note over NativeTV: Field stays focused across<br/>repeated emoji replacements
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Reviews (2): Last reviewed commit: "fix: restore rename placeholder and sing..." | Re-trigger Greptile

Comment thread Sources/ContentView.swift
Copy link
Copy Markdown

@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

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="Sources/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:3801">
P3: Use `target.placeholder` for the native rename input placeholder so the localized empty-state hint is preserved.</violation>
</file>

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

Comment thread Sources/ContentView.swift Outdated
Copy link
Copy Markdown

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 4070-4078: textDidChange currently passes pasted newlines through;
ensure the rename input remains single-line by stripping any newline characters
before updating state: in textDidChange(_:) sanitize textView.string (e.g.,
replace "\n" and "\r" with a single space or remove them) and assign the
sanitized value to parent.text and back into textView.string so the view shows
the single-line text; then continue to call enclosingInputView(for:) ->
syncTextViewFrameToContentSize() and invalidateTextInputGeometry(for:) as
before. Reference textDidChange(_:), enclosingInputView(for:),
container.syncTextViewFrameToContentSize(), and
invalidateTextInputGeometry(for:) when applying this fix.
- Around line 3799-3803: The placeholder for the rename field was removed;
restore it by passing the existing CommandPaletteRenameTarget.placeholder into
the CommandPaletteTextInputRepresentable initializer instead of the empty string
literal (""); update the initializer call in the HStack that creates
CommandPaletteTextInputRepresentable to use
CommandPaletteRenameTarget.placeholder so the localized prompt reappears when
the draft is blank.
- Around line 6929-6933: The deleteBackward currently grabs whichever NSTextView
is firstResponder (via observedWindow ?? NSApp.keyWindow ?? NSApp.mainWindow ->
window.firstResponder), which can target other editors; restrict the deletion to
the palette-owned editor by checking that the firstResponder text view is the
palette's rename field (e.g., compare to a stored reference like
commandPaletteRenameEditor or verify the responder is inside the palette
view/class) before calling deleteBackward and updating
commandPaletteRenameDraft; update the logic that references observedWindow,
window.firstResponder, NSTextView, deleteBackward, and commandPaletteRenameDraft
so the delete action runs only when the responder is the actual command-palette
rename NSTextView.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6d8fcd1-f315-414b-9980-d7e5f94e7b0e

📥 Commits

Reviewing files that changed from the base of the PR and between e9afc22 and f9ae7c0.

📒 Files selected for processing (2)
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift

Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
@pandec
Copy link
Copy Markdown
Author

pandec commented Mar 28, 2026

@greptile

Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/ContentView.swift (1)

1206-1221: ⚠️ Potential issue | 🟡 Minor

Scope the ">" selection normalization to the command palette search field.

The command-mode prefix semantics (restoring caret-at-end for ">" queries) are specific to the palette search field. However, isPaletteTextInputFirstResponder(editor) at line 1209 matches any editable NSTextView descendant of containerView when it's not a field editor (line 1086 in the helper), which could include workspace/tab rename fields. A rename field starting with ">" would incorrectly have its full selection normalized away on refocus.

Use isPaletteFieldEditor(editor) instead to ensure this normalization applies only to the palette's search field.

🛠️ Suggested fix
         guard let window,
               let editor = window.firstResponder as? NSTextView,
-              isPaletteTextInputFirstResponder(editor) else { return }
+              isPaletteFieldEditor(editor) else { return }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 1206 - 1221, The normalization in
normalizeSelectionAfterProgrammaticFocus is currently gated by
isPaletteTextInputFirstResponder(editor) which matches any editable NSTextView
descendant and can wrongly affect rename fields; change the guard to use
isPaletteFieldEditor(editor) instead so the caret-at-end restoration only runs
for the palette's search field (update the guard that checks
isPaletteTextInputFirstResponder to call isPaletteFieldEditor with the same
editor variable).
♻️ Duplicate comments (1)
Sources/ContentView.swift (1)

1129-1154: ⚠️ Potential issue | 🟠 Major

Guard the async focus retries on palette/window state.

updateFocusLockForWindowState() now cancels scheduledFocusWorkItem, but the asyncAfter tail here is independent. If the window resigns key or the palette hides after the first retry starts, a late callback can still run makeFirstResponder(...) and yank focus back from the emoji/system panel.

🛠️ Suggested fix
     private func focusIntoPalette(retries: Int) {
-        guard let window else { return }
+        guard let window, isPaletteVisible, window.isKeyWindow else { return }
         if isPaletteTextInputFirstResponder(window.firstResponder) {
             return
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 1129 - 1154, The async retry in
focusIntoPalette can run after the palette/window state changes because it uses
DispatchQueue.main.asyncAfter directly; replace that tail with a cancellable
DispatchWorkItem stored in scheduledFocusWorkItem (the same token
updateFocusLockForWindowState cancels) or otherwise guard the retry by checking
the palette/window state before attempting focus: capture self weakly, create a
DispatchWorkItem that first verifies the window is still key and the palette is
visible (the same conditions used elsewhere), then calls
focusIntoPalette(retries: -1); assign that work item to scheduledFocusWorkItem
so updateFocusLockForWindowState can cancel it, and when canceled do not call
makeFirstResponder on containerView/firstEditableTextInputView.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 1206-1221: The normalization in
normalizeSelectionAfterProgrammaticFocus is currently gated by
isPaletteTextInputFirstResponder(editor) which matches any editable NSTextView
descendant and can wrongly affect rename fields; change the guard to use
isPaletteFieldEditor(editor) instead so the caret-at-end restoration only runs
for the palette's search field (update the guard that checks
isPaletteTextInputFirstResponder to call isPaletteFieldEditor with the same
editor variable).

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 1129-1154: The async retry in focusIntoPalette can run after the
palette/window state changes because it uses DispatchQueue.main.asyncAfter
directly; replace that tail with a cancellable DispatchWorkItem stored in
scheduledFocusWorkItem (the same token updateFocusLockForWindowState cancels) or
otherwise guard the retry by checking the palette/window state before attempting
focus: capture self weakly, create a DispatchWorkItem that first verifies the
window is still key and the palette is visible (the same conditions used
elsewhere), then calls focusIntoPalette(retries: -1); assign that work item to
scheduledFocusWorkItem so updateFocusLockForWindowState can cancel it, and when
canceled do not call makeFirstResponder on
containerView/firstEditableTextInputView.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6150eadf-02e7-4095-8bc4-6d63101f3ef6

📥 Commits

Reviewing files that changed from the base of the PR and between f9ae7c0 and 0b07651.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

@pandec pandec closed this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant