Skip to content

fix(app): submit on Magic Keyboard Enter in the iPadOS PWA composer - #1099

Open
salemsayed wants to merge 1 commit into
get-bb:mainfrom
salemsayed:bb/fix-ipad-pwa-composer-on-upstream-thr_6vtsdfa5qk
Open

fix(app): submit on Magic Keyboard Enter in the iPadOS PWA composer#1099
salemsayed wants to merge 1 commit into
get-bb:mainfrom
salemsayed:bb/fix-ipad-pwa-composer-on-upstream-thr_6vtsdfa5qk

Conversation

@salemsayed

Copy link
Copy Markdown

Problem

In the installed iPad PWA, there is no way to send a prompt from a Magic Keyboard. Pressing Enter in the composer always inserts a newline, and the only way to submit is to tap the send button.

The composer gates Enter-to-submit on pointer coarseness alone:

const canSubmitWithEnterKey = !isPointerCoarse;

An iPad reports a coarse pointer whether or not a hardware keyboard is attached, so the hardware Enter is permanently routed to the newline path. Making this device-wide would break the software keyboard, whose Return must stay a newline.

Two details make the physical key hard to detect on this platform:

  • ProseMirror deliberately defers iOS Enter handling and later replays a synthetic Enter into handleKeyDown, so the software keyboard can finish its own DOM mutation first. That synthetic event no longer carries the physical code.
  • The iPad software keyboard's Return arrives with an empty code, while a Magic Keyboard Enter arrives as code: "Enter" (or "NumpadEnter").

Fix

A custom ProseMirror DOM keydown hook now looks at the original event before ProseMirror's deferral, and only on iPadOS WebKit. When that original event carries a physical Enter/NumpadEnter code, the existing key handler is invoked with isOriginalIPadHardwareEnter, which enables Enter-to-submit for that one event:

const canSubmitWithEnterKey = !isPointerCoarse || isOriginalIPadHardwareEnter;

Device detection combines an Apple WebKit vendor/UA check with either the legacy iPad platform or the modern desktop-like MacIntel platform plus maxTouchPoints > 2, which is what distinguishes current iPadOS from macOS.

Everything else keeps ProseMirror's normal path, including the software keyboard's Return, which still inserts a newline.

Steer/queue mapping is unchanged

No submit routing changed. canSubmitWithEnterKey simply moved from a component-level constant to a per-event value inside the key handler.

  • steerActiveThreadOnEnter off (default): Enter queues a follow-up, Command+Enter steers.
  • steerActiveThreadOnEnter on: Enter steers, Command+Enter queues.
  • Shift+Enter inserts a newline; in zen mode unmodified Enter inserts a newline too.
  • On iPadOS, a Magic Keyboard Enter/Command+Enter now reaches exactly these existing paths instead of being swallowed.

FollowUpPromptBox.test.tsx consolidates the two overlapping shortcut tests into one parameterized case that asserts both settings, including the scroll-to-bottom behavior that differs between queue and steer.

Android and desktop safeguards

The interception is scoped as narrowly as possible, and both non-iPad coarse-pointer cases are covered by tests:

  • Android (Pixel Tablet, Google Inc. vendor, Linux armv8l) reports code: "Enter" from its software keyboard. It fails the Apple WebKit check, is never intercepted, and keeps Return as a newline.
  • Windows touch hybrids (Win32, maxTouchPoints: 10) likewise fail the vendor check and are untouched.
  • Fine-pointer desktop is entirely unaffected — the hook is a no-op there, and Enter-to-submit continues to run through the existing !isPointerCoarse path.

IME and typeahead

Composition is excluded on several fronts, so confirming a candidate never submits:

  • view.composing, event.isComposing, and event.keyCode === 229 all bail out.
  • The hook mirrors ProseMirror's own Safari compositionendkeydown safeguard with a 500 ms window. Because this DOM hook runs before ProseMirror's keydown handler, skipping that safeguard would submit an IME candidate confirmation.
  • An event suppressed by that window is recorded in a WeakSet, so if it later arrives through the normal handler it is still refused rather than double-processed.

Typeahead is unaffected: with an open command or mention menu, a Magic Keyboard Enter applies the highlighted suggestion (//review , with the mention resource attached) and does not submit, matching the fine-pointer behavior.

Performance

  • isIPadOSWebKit() is computed once per mount via useMemo. The value is stable for the lifetime of the page, so no additional media-query or event listener is registered.
  • The keydown hook returns after a few boolean checks for every non-iPad platform and every non-Enter key, so no per-keystroke cost is added anywhere else.
  • The composition guard is an O(1) timestamp comparison plus a WeakSet lookup. Keying the set on the event object means entries are collected along with the event, so it cannot grow unbounded.
  • canSubmitWithEnterKey left the useCallback dependency list and was replaced by isPointerCoarse, so handler identity churns no more often than before.
  • Assigning handleEditorKeyDownRef moved from useEffect to useLayoutEffect, so the ref is current before any keydown can be dispatched after a commit and a stale handler cannot fire in the same frame.

Documentation

Per the repository's rule that a user-facing behavior change updates its discoverable surfaces in the same change, the Enter behavior is now described in:

  • docs/configuration.md
  • the built-in bb-cli skill (SKILL.md and references/app-settings.md)
  • the bb guide template bb-guide-customization.md and its generated counterpart

Each now states the typeahead exception, Shift+Enter and zen-mode newlines, the coarse-pointer software-keyboard path, and the iPadOS Magic Keyboard behavior.

Validation

  • Tests: 317 files and 2,384 app tests passed.
  • Typecheck: app and templates passed.
  • Lint: app passed, with existing warnings only.
  • Diff check: passed.
  • Device verification: confirmed on a physical iPad running the installed PWA — a Magic Keyboard Enter submits, and the software keyboard's Return still inserts a newline.

New coverage in PromptBoxInternal.test.tsx spans fine-pointer Enter, Magic Keyboard Enter, software-keyboard Enter, Android, a Windows touch hybrid, Shift+Enter, Command+Enter, IME composition, the post-compositionend window, zen mode, and typeahead application.

🤖 Generated with Claude Code

The composer gated Enter-to-submit on a coarse pointer, so an iPad always
took the newline path even with a Magic Keyboard attached. In the iPad PWA
there was then no way to send a prompt from the hardware keyboard.

On iPadOS WebKit only, a ProseMirror DOM keydown hook now inspects the
original event's physical code. A Magic Keyboard Enter reports
code="Enter"/"NumpadEnter" and submits; the software keyboard's Return
reports an empty code and still inserts a newline. Other platforms,
including Android and coarse-pointer Windows hybrids, stay entirely on
ProseMirror's normal path.

The steer/queue mapping is unchanged: Enter and Command+Enter keep their
existing roles under steerActiveThreadOnEnter, Shift+Enter and zen-mode
Enter stay newlines, and an open typeahead menu still consumes Enter to
apply its suggestion.

IME composition is excluded via view.composing, event.isComposing and
keyCode 229, plus ProseMirror's Safari compositionend -> keydown window,
so confirming a candidate never submits.

Docs, the bb-cli skill, and the bb guide template describe the resulting
Enter behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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