Home: the emoji button opens the picker for the message box; the + picker opens without a delay

Merged
TE
17d ago
+235-17

Problem

In the cmux-next Home chat (the Chief's iMessage-style view):

  • Emoji button. Clicks often did nothing, or the chosen emoji never reached the message box. It only worked when the box already had the keyboard. The button focused the box and called orderFrontCharacterPalette in the same click. AppKit moves the key window's text input context to a new first responder only after that event, so the picker opened for the previous responder (the transcript or the pane).
  • "+" (add attachment) button. The sheet came up slowly. Each click built a new NSOpenPanel and opened it from a Task, one run-loop turn later. On macOS 27.0.1, in a bare app, NSOpenPanel() takes about 130 ms of main-thread time (about 400 ms for the first one in a process), and beginSheetModal adds about 270 ms. That is 0.7 s or more before the sheet appears.

Change

  • ChatController+Emoji.swift: the button makes the box first responder (and makes its window key), then asks for the picker one turn later, once the box's input context is current. A box that already has the keyboard opens the picker at once. The picker call goes through openEmojiPalette so tests can count it.
  • HomeAttachmentPanel.swift: one open panel is made while Home is idle (a DemandTimer deadline, after a second with no input, at most 10 checks) and reused. The click opens it as a sheet in its own turn. A second click while the sheet is open does nothing.

Verification

  • Commit 1 adds EmojiButtonTests without the fix. On the fleet it fails as intended: Expectation failed: (opened.count โ†’ 1) == 0, meaning the picker was opened in the click's own turn.
  • With the fix, on the fleet (Xcode 26.6, package-test-lane.sh suite, run on the pre-rebase SHAs; the rebase onto the current base was clean):
    • EmojiButtonTests and DropFocusTests pass in Packages/Shared/CmuxMessagesLab.
    • HomeComposerInlineImageTests pass in Packages/macOS/CmuxNext, so the "+" change compiles.
  • scripts/cmux-next/check-concurrency.sh reports nothing in the changed files.
  • Review round (commit 4, 43e669f):
    • The emoji button keeps the draft's caret (the tapback menu's emoji item shares the path).
    • It still asks for the picker when the field cannot take the keyboard.
    • It waits for didBecomeKey while the app is still activating.
    • The attach panel has no process-wide showing flag, so a torn-down sheet can't disable "+" and a second window gets its own panel.
    • The warm-up counts input only while cmux is in front.
    • Fleet on 43e669f: EmojiButtonTests (3 tests, including the new caret test), DropFocusTests and HomeComposerInlineImageTests pass.
  • Not done: dogfood in a tagged build. The panel timings come from a standalone bench, not from inside the app.

No user-facing strings changed, so no localization audit was needed.

๐Ÿค– Generated with Claude Code