fix(chat-ui): let ⌘B reach the sidebar toggle when nothing is selected in the composer
Summary
- On macOS, with the prompt composer focused and nothing selected, ⌘B toggles the workspace sidebar again.
- Every other bold/italic shortcut behaves as on
main: ⌘B on a selection, ⌘I, and Ctrl+B / Ctrl+I on Windows and Linux format in the editor and are kept from app hotkeys.
Why / Context
#9128 added rich text to the composer and stopped propagation of every ⌘B and ⌘I press so formatting would not also trigger app hotkeys. That also meant the sidebar could not be toggled while the composer had focus. The New Workspace home opens with the cursor in the composer, so ⌘B appeared broken there until focus moved.
Only ⌘B on macOS collides with an app binding. Windows and Linux bind the sidebar to Ctrl+Shift+B, which the composer never matched. ⌘I's app binding (terminal rich input) runs only while a terminal pane is active, which a composer pane never is. So only that one chord is handed back.
How It Works
getFormatShortcutTarget(event, { isApple, hasSelection }) (new, pure) returns:
"app"for ⌘B with a caret on macOS. The composer'sKEY_DOWN_COMMANDhandler (COMMAND_PRIORITY_LOW) then callspreventDefault()and returnstrue. Returningtrueskips Lexical's bold shortcut (aKEY_DOWN_COMMANDlistener atEDITORpriority);preventDefault()blocks Chromium's nativeformatBoldbeforeinput. The event still bubbles todocument, whereuseHotkey(enableOnContentEditable: true; react-hotkeys-hook does not checkdefaultPrevented) toggles the sidebar."editor"for every other bold/italic shortcut. The handler stops propagation and lets Lexical format, same asmain.nullfor anything else, including Ctrl+B on macOS (native caret move) and Ctrl+Shift+B.
Matching uses Lexical's isExactShortcutMatch with the same modifier mask Lexical formats with, so non-Latin layouts match by key code.
Manual QA Checklist
Driven over CDP with real Input.dispatchKeyEvent / mouse input in the desktop dev app (new shell, /new-workspace home composer), on the first commit. Baseline taken by hot-swapping the main version of ComposerBody.tsx into the same running app. The second commit leaves the macOS ⌘B path unchanged:
| Step | main | This PR |
|---|---|---|
Type hello world, ⌘B with caret | Sidebar stays open; next typed character is bold | Sidebar collapses; nothing bold |
Double-click hello, ⌘B | hello bold, sidebar unchanged | hello bold, sidebar unchanged |
console.error during the run | none | none |
- macOS: ⌘B with caret toggles the workspace sidebar from the home composer
- macOS: ⌘B on a selection bolds it and does not toggle the sidebar
- macOS: ⌘I with caret starts italic and does not reach app hotkeys (not re-run live after the second commit; covered by
getFormatShortcutTargettests) - Windows / Linux: Ctrl+B / Ctrl+I unchanged from
main(not run; covered by the editor tests, which run on happy-dom's non-Apple platform)
Testing
bun test --isolateinpackages/chat-ui(Bun 1.4.3): 100 pass, 0 fail.getFormatShortcutTarget.test.ts: the decision table for both platforms (⌘B caret / selection / non-Latin layout, ⌘I, ⌘⇧B, Ctrl+B on macOS, Ctrl+B / Ctrl+I / Ctrl+Shift+B elsewhere, unrelated keys).ComposerBody.test.tsx: in the real editor, mod+B / mod+I on a selection format and stay out ofdocument; Ctrl+B / Ctrl+I with a caret format the next text and stay out ofdocument.
tsc --noEmitinpackages/chat-uibiome checkon the changed files
Known Limitations
- On macOS, ⌘B with only a caret no longer turns on bold for the next typed text, including inside a bold run; select the text first.
- happy-dom reports a non-Apple platform, so the editor tests cannot reach the macOS
"app"wiring (preventDefault+return true). That path is covered by the decision-table test and the CDP run above. - The two selection tests print a Lexical
Attempted to assign to readonly propertyerror without failing. happy-dom dispatchesselectionchangesynchronously inside Lexical's commit (Chromium queues it). It does not occur in the app.
Follow-ups
- The Channels composer (
MessageComposerBody) has no guard at all onmain: ⌘B on a selection there bolds the text and also toggles the workspace sidebar. A shared helper for both composers would fix it.