fix(desktop): make ⌘K open the composer link editor for selections#1644
Conversation
The formatting toolbar's link button has always advertised ⌘K, and the shortcuts panel lists 'Insert link ⌘K' — but the binding was never implemented. The window-level quick-search handler in AppShell owned ⌘K unconditionally, so pressing it with composer text selected opened the search dialog instead of the add-link modal. Bind ⌘K in the editor's raw ProseMirror handleKeyDown hook (the same layer as the ArrowUp edit-last-message handling) and consume it only when it applies: text is selected, or the caret sits inside an existing link. A bare caret still falls through to quick search, preserving the app-wide ⌘K muscle memory. AppShell's global handler now respects event.defaultPrevented so the composer wins when it claims the key. - useRichTextEditor: new onLinkShortcut option, handled in editorProps.handleKeyDown with repeat/IME-composition guards; plain Ctrl+K on macOS is still rejected by hasPrimaryShortcutModifier, so the Emacs kill-line binding is untouched - useLinkEditor: new openFromShortcut() — opens the dialog only when a selection or caret-adjacent link exists, returns whether it consumed the shortcut, and dismisses the hover card first - MessageComposer + ForumComposer: wired via the existing ref pattern - AppShell: skip shortcut handling for defaultPrevented and repeat events - keyboard-shortcuts: format-link description now matches the behavior - e2e: new composer-link-shortcut.spec.ts covering all six states (selection → add dialog, caret-in-link → edit dialog, empty caret → search, unselected draft → search, mac Ctrl+K kill-line regression, focus outside composer → search) Co-authored-by: Aaron Goldsmith <aarong@squareup.com> Signed-off-by: Aaron Goldsmith <aarong@squareup.com>
wesbillman
left a comment
There was a problem hiding this comment.
Approve — reviewed on behalf of Wes (verified locally at head aba9752).
Verification: checked out the PR head and ran all 24 search-related smoke e2e tests (every existing ⌘K quick-search flow) plus the 6 new composer-link-shortcut specs — all green; tsc clean.
Why the ⌘K change is safe: the editor consumes the key only when openFromShortcut() finds a selection or caret-on-link; otherwise it returns false, no preventDefault(), and the event bubbles to AppShell's quick-search listener exactly as before. ⌘⇧K (new DM) and macOS Ctrl+K kill-line verified untouched. The selection→link / no-selection→palette discriminator matches GitHub/Notion/Linear precedent, and it's the behavior the toolbar tooltip and shortcuts panel have advertised all along.
One note: the main CI workflow was stuck at action_required at review time — get the full suite green on record before merging.
Problem
The formatting toolbar's link button advertises ⌘K in its tooltip, and the keyboard-shortcuts settings panel lists "Insert link ⌘K" — but the binding was never implemented. The window-level quick-search handler in
AppShellowned ⌘K unconditionally, so pressing it with composer text selected opened the channel search dialog instead of the add-link modal.(A code comment even said "Media paste + ⌘K link shortcut via Tiptap editorProps" — but only the paste half was ever written.)
Fix
Bind ⌘K in the editor's raw ProseMirror
handleKeyDownhook (the same proven layer as the ArrowUp edit-last-message handling) and consume it only when it applies:Ctrl+Kon macOSThe conditional fall-through deliberately preserves the app-wide ⌘K quick-search muscle memory — the shortcut only claims the key when there's something to link.
Changes
useRichTextEditor.ts— newonLinkShortcutoption handled ineditorProps.handleKeyDown, withevent.repeat/event.isComposingguards.hasPrimaryShortcutModifierstill rejects Ctrl on macOS, so themacEmacsTextShortcutsCtrl-K kill-line binding is untouched.useLinkEditor.tsx— newopenFromShortcut(): opens the dialog only when a selection or caret-adjacent link exists, returns whether it consumed the shortcut, and dismisses the link hover card first.MessageComposer.tsx+ForumComposer.tsx— wired via the existingonEditLinkRefpattern (bothuseRichTextEditorconsumers covered).AppShell.tsx— global shortcut handler now bails onevent.defaultPrevented(composer wins when it claims the key) and onevent.repeat(held ⌘K can't stack quick-search over the link dialog).keyboard-shortcuts.ts—format-linkdescription updated to match actual behavior.composer-link-shortcut.spec.ts— new Playwright spec covering all six states in the table above, registered in the smoke project.Testing
Ctrl+Kkill-line regression guard)tscclean · biome clean (6 pre-existing warnings, unchanged)Mechanism notes (for reviewers)
ProseMirror attaches its keydown listener directly to the editor element; when
handleKeyDownreturnstrue, ProseMirror callsevent.preventDefault()and the event still bubbles to the window withdefaultPrevented === true, which the AppShell listener now respects. When the handler returnsfalsethe event bubbles untouched and quick search opens exactly as before.